如何在React状态中更新树形数据结构
解决React Table树形数据更新与渲染问题
核心问题梳理
你遇到的问题主要有两个关键点:一是Immer对类实例的处理导致初始渲染不更新,二是父节点总和的计算逻辑错误导致状态更新异常。以下是具体的解决方案:
1. 修正Immer对树形节点的处理
Immer对类实例的代理支持有限,虽然你已经根据文档调整解决了渲染问题,但改用纯对象结构替代类实例会更稳定,彻底避免Immer的类兼容问题:
// 用类型定义代替类 type TreeNode = { name: string; isLeaf: boolean; subtotal: number | null; value: number | null; children: TreeNode[]; }; // 创建节点的工具函数 const createTreeNode = (name: string, children?: TreeNode[]): TreeNode => ({ name, isLeaf: !children?.length, subtotal: null, value: null, children: children || [] });
用纯对象后,Immer可以完美处理不可变更新,无需额外配置。
2. 修复父节点总和的计算逻辑
你之前的代码是直接累加当前修改的值,这会导致总和越算越错——正确的做法是重新计算父节点所有子节点的value之和,而不是累加单次修改值:
const updateTableData = (rowId: string, val: number) => { setTableData( produce((draft) => { // 解析rowId为节点路径(比如'0.0'拆成[0, 0]) const path = rowId.split('.').map(Number); let targetNode: TreeNode | undefined = draft; let parentNode: TreeNode | null = null; // 遍历路径找到目标子节点和其父节点 for (let i = 0; i < path.length; i++) { parentNode = targetNode; targetNode = targetNode.children[path[i]]; if (!targetNode) break; } // 更新子节点的value if (targetNode) { targetNode.value = val; } // 重新计算父节点的subtotal if (parentNode) { parentNode.subtotal = parentNode.children.reduce((total, child) => { return total + (child.value ?? 0); }, 0); } }) ); };
3. 确保React Table列配置正确
父节点的列需要正确读取subtotal属性,子节点的列渲染输入框时要绑定正确的更新逻辑:
const columns = useMemo(() => [ { accessorKey: 'name', header: '节点名称', }, { accessorKey: 'value', header: '数值/总和', cell: ({ row }) => { if (row.original.isLeaf) { return ( <input type="number" value={row.original.value ?? ''} onChange={(e) => updateTableData(row.id, Number(e.target.value))} /> ); } else { return <span>{row.original.subtotal ?? 0}</span>; } }, }, ], []);
关键注意事项
- 嵌套状态更新必须保证不可变性,Immer能帮你简化这个过程,但纯对象比类实例更适合Immer的代理机制。
- 父节点总和必须实时计算所有子节点的值,不能依赖单次修改的增量,否则多次编辑后数据会失真。
- 确保rowId的路径解析正确,React Table的row.id格式是节点在树形结构中的索引路径,拆分后要对应到数组的索引位置。
内容的提问来源于stack exchange,提问作者Eric Xin Zhang
相关产品推荐
相关产品推荐

