You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

solid-record的redo函数无法正确设置状态问题求助

Solid-Record节点重父化Redo异常问题解决

问题描述

使用solid-record构建思维导图应用,实现节点重父化的撤销/redo功能时,撤销操作可正常恢复初始状态,但redo后节点0的子节点列表变为[3],而非预期的[1,2]。移除修改节点3parentid的操作后,redo功能恢复正常。

复现代码

import { Component, For } from 'solid-js'
import { createStore } from 'solid-js/store'
import { createHistory } from 'solid-record'

type Node = {
  id: number,
  children: Array<number>
  parentid: number
}

const initialValue = [
  { id: 0, children: [1, 2, 3], parentid: -1 },
  { id: 1, children: [], parentid: 0 },
  { id: 2, children: [], parentid: 0 },
  { id: 3, children: [], parentid: 0 },
]

const [state, setState] = createStore(initialValue)
const undoHistory = createHistory()

const changeState = () => {
  undoHistory.batch()

  const nodeid = 3
  const oldparentid = 0
  const newparentid = 2
  let node = state[nodeid]
  let oldparent = state[oldparentid]
  let newparent = state[newparentid]

  // first remove node form parent's children
  undoHistory.add(setState, n => n.id === node.parentid, 'children', oldparent.children.filter(n => n !== node.id))
  // then add to new parent's children
  undoHistory.add(setState, n => n.id === newparent.id, 'children', [...newparent.children, node.id])
  // lastly, point to new parent
  undoHistory.add(setState, n => n.id === node.id, 'parentid', newparent.id)
  undoHistory.unbatch()
}

const App: Component = () => {
  return (
    <>
      <For each={state}>{(node: Node) => <div>{`id: ${node.id}, parentid: ${node.parentid}, children: ${node.children}`}</div>}</For>
      <button onClick={changeState}>Change Parent of 3</button>
      <button onClick={() => undoHistory.undo()} disabled={!undoHistory.isUndoable()}>Undo</button>
      <button onClick={() => undoHistory.redo()} disabled={!undoHistory.isRedoable()}>Redo</button>
    </>
  );
};

export default App;

问题分析

核心问题出在第一个操作的predicate函数n => n.id === node.parentid:

  • node是solid-store返回的代理对象,后续修改node.parentid的操作会改变这个对象的属性值。
  • solid-record在batch中记录的是函数本身,redo时函数执行会依赖当前状态下的node.parentid值,而非操作时的原始值,导致操作目标错误指向新父节点(节点2),而非原始父节点(节点0),最终造成节点0的子节点状态更新异常。

解决方案

将第一个操作的predicate改为使用提前保存的常量oldparentid,避免依赖动态变化的node.parentid。修改后的changeState函数如下:

const changeState = () => {
  undoHistory.batch()

  const nodeid = 3
  const oldparentid = 0
  const newparentid = 2
  let node = state[nodeid]
  let oldparent = state[oldparentid]
  let newparent = state[newparentid]

  // 使用oldparentid替代node.parentid,确保操作目标始终是原始父节点
  undoHistory.add(setState, n => n.id === oldparentid, 'children', oldparent.children.filter(n => n !== node.id))
  undoHistory.add(setState, n => n.id === newparent.id, 'children', [...newparent.children, node.id])
  undoHistory.add(setState, n => n.id === node.id, 'parentid', newparent.id)
  undoHistory.unbatch()
}

此修改确保无论node.parentid后续如何变化,第一个操作的predicate都会稳定指向原始父节点(节点0),redo时操作目标正确,状态更新符合预期。

内容的提问来源于stack exchange,提问作者Franck Dervaux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:34:51