React递归函数getBranches在Chrome调试VM下返回重复数据问题
实现需求
现有任意深度的树状数据结构,需要从中提取所有非叶子分支节点组成列表,作为props传递给子组件,使用的数据集与相关代码如下。
问题描述
运行时返回的分支数组存在重复数据,经排查发现getBranches()函数会被Chrome调试VM额外调用,向已存在的数组中重复追加相同数据,控制台调用计数可参考附图。请问当前实现思路是否存在根本性错误?该如何修复该问题?
树结构数据代码
const treeData = { 'first-level-node-1': { // 节点key label: 'Regija', type: TYPES[1], index: 0, // 渲染顺序 url: 'http://opa.com', nodes: { 'second-level-node-1': { label: 'Bolnica BN', index: 0, type: 1, nodes: { 'third-level-node-1': { label: 'Konektor Sysmex', index: 0, nodes: {}, type: TYPES[0] }, }, }, }, }, 'first-level-node-2': { label: 'Regija 2', index: 1, type: TYPES[1], nodes: { '2-1': { label: 'Dz Trebinje', index: 0, type: 1, nodes: { '3-1': { label: 'Konektor Biomerux', index: 0, hasNodes: false, type: TYPES[0], } } } } }, };
递归函数getBranches代码
const getBranches = (treeData) => { console.log('i= ',i++) Object.keys(treeData).forEach((key) => { if (treeData[key].type!==TYPES[0]) { branches.push({key: key, label: treeData[key].label}); console.log({key: key, label: treeData[key].label}) treeData[key].nodes && getBranches(treeData[key].nodes) }; }); return [...branches]; };
React组件代码
const Sites = () => { const branches = getBranches(treeData); console.log(branches); //const [data, setData] = useState(treeData); return ( <Container fluid="md"> <Row> <Col> <MyTreeView data={treeData}/> </Col> <Col> <BranchView sites={treeData} parentList={branches} /> </Col> </Row> </Container> ); };
问题根因
当前实现存在根本性错误,和Chrome调试VM调用没有直接关系:
- 核心问题是外部状态污染:
branches数组定义在getBranches函数外部,属于闭包/全局共享变量。React组件每次重渲染(包括严格模式下的预执行、调试器的快照执行)都会重新调用getBranches,函数不会重置branches数组,只会不断往已有数组里追加数据,直接导致重复。 - 递归逻辑无状态隔离:递归过程没有在每次函数调用时维护独立的结果集合,完全依赖外部变量存储结果,函数本身不是纯函数,只要多次执行就必然出现数据累加重复。
所谓Chrome调试VM额外调用只是触发问题的场景,不是根源。哪怕没有调试行为,组件只要触发任意重渲染,都会出现重复数据。
修复方案
把结果数组放到函数内部初始化,递归时合并子层结果,完全消除外部依赖,让函数成为输入输出稳定的纯函数:
const getBranches = (treeData) => { // 每次调用时独立初始化当前层结果,完全隔离外部状态 const currentBranches = []; Object.keys(treeData).forEach((key) => { const node = treeData[key]; if (node.type !== TYPES[0]) { currentBranches.push({ key, label: node.label }); // 递归获取子层分支,合并到当前结果 if (node.nodes) { currentBranches.push(...getBranches(node.nodes)); } } }); return currentBranches; };
额外性能优化:在React函数组件中,建议用useMemo包裹计算逻辑,避免每次重渲染都重复遍历整棵树:
const Sites = () => { // 空依赖数组表示仅在组件首次挂载时计算一次 const branches = useMemo(() => getBranches(treeData), []); // 其余组件逻辑保持不变 }
补充说明
分支节点与叶子节点通过节点的type属性区分,函数重复调用计数参考截图:
内容的提问来源于stack exchange,提问作者D Kuzmanovic
相关产品推荐
相关产品推荐

