React结合d3.js实现可折叠树时API动态拉取子节点后视图不更新如何解决
你遇到的问题主要是两个根源:
- React的state更新要求不可变修改,原D3示例直接修改节点对象的children/_children属性属于对state的可变修改,React无法检测到state变化,不会触发组件重渲染
- 异步请求拿到数据后你没有把新的子节点正确同步到state的树结构中,也没有建立state变化到D3重绘的关联,所以D3不会更新图表
具体解决步骤
1. 实现树结构的不可变更新工具函数
不要直接修改原state中的树节点属性,通过递归查找目标节点,返回全新的树结构用于更新state:
// 入参:原树结构、目标节点唯一ID、要挂载的新子节点数组 const updateTreeNode = (tree, targetNodeId, newChildren) => { // 找到目标节点,返回新的节点对象,不修改原对象 if (tree.id === targetNodeId) { return { ...tree, children: newChildren, // 已经拉取过的子节点可存到_children字段,后续折叠后再展开不用重复请求 _children: newChildren || tree._children } } // 递归遍历子节点 if (tree.children) { return { ...tree, children: tree.children.map(child => updateTreeNode(child, targetNodeId, newChildren)) } } return tree }
2. 调整点击事件逻辑
所有节点修改操作都走不可变更新,异步请求完成后先更新state:
const click = async (d) => { // 场景1:当前节点已展开,点击后折叠 if (d.children) { const newTree = updateTreeNode(treeData, d.id, null) setTreeData(newTree) return } // 场景2:当前节点已拉取过子节点存在_children中,直接展开 if (d._children) { const newTree = updateTreeNode(treeData, d.id, d._children) setTreeData(newTree) return } // 场景3:未拉取过子节点,发起API请求 const res = await fetch(`你的API接口地址?parentId=${d.id}`) const childNodes = await res.json() // 将拉取到的子节点挂载到点击节点上,更新state const newTree = updateTreeNode(treeData, d.id, childNodes) setTreeData(newTree) }
3. 建立state到D3渲染的关联
通过useEffect监听树数据的变化,每次state更新自动触发D3的重绘逻辑:
// treeData是你存在state中的树结构数据 useEffect(() => { // 调用D3的update方法,传入最新的树数据重新渲染 update(treeData) }, [treeData])
注意事项
- 每个树节点必须配置唯一的
id字段,用于递归查找定位需要更新的目标节点 - D3的
update方法需要完善enter/update/exit的三段式处理逻辑,保证新增节点可以正常插入,旧节点位置可以正常更新 - 所有对树结构的修改都要走不可变更新逻辑,不要直接修改state中原树对象的属性
内容的提问来源于stack exchange,提问作者Kishan Vikani
相关产品推荐
相关产品推荐

