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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 16:20:23