如何在rc-tree中实现勾选节点时联动修改父子节点checked属性
解决rc-tree勾选节点同步修改父子节点checked属性的问题
我来帮你搞定这个rc-tree的勾选同步问题,分两步走:先解决你遇到的ref获取节点错误的问题,再实现勾选时同步父子节点checked属性的逻辑。
一、先修正Ref获取节点的问题
你现在遇到onCheck总是拿到最后一个节点的问题,是因为把所有TreeNode的ref都赋值给了同一个this.myRef,循环里后面的节点会直接覆盖前面的。正确的做法是用一个对象来存储每个节点的ref,以节点的key作为唯一标识:
class YourTreeComponent extends React.Component { constructor(props) { super(props); this.nodeRefs = {}; // 用对象存储每个节点的ref,key对应节点的key this.state = { treeData: [...yourInitialTreeData], // 初始化树数据 checkedKeys: [], expandedKeys: [], autoExpandParent: true }; } render() { const loop = (data) => { return data.map((item) => { const treeNodeProps = { ref: node => this.nodeRefs[item.key] = node, // 将每个节点的ref存入nodeRefs title: item.name, key: item.key, checked: item.checked }; if (item.children && item.children.length) { return ( <TreeNode {...treeNodeProps}> {loop(item.children)} </TreeNode> ); } return <TreeNode {...treeNodeProps} isLeaf={item.isLeaf} />; }); }; return ( <div className="draggable-container"> <Tree expandedKeys={this.state.expandedKeys} onExpand={this.onExpand} autoExpandParent={this.state.autoExpandParent} draggable onDragStart={this.onDragStart} onDragEnter={this.onDragEnter} onDrop={this.onDrop} onSelect={this.onSelect} checkable onCheck={this.onCheck} checkedKeys={this.state.checkedKeys} > {loop(this.state.treeData)} </Tree> </div> ); } }
不过其实更推荐直接通过onCheck事件的参数获取当前节点——rc-tree的onCheck第二个参数event里已经包含了当前操作的节点对象event.node,不需要依赖ref也能拿到目标节点。
二、实现勾选同步父子节点checked的逻辑
你的需求是勾选任意节点时,同步修改该节点、所有父节点、所有子节点的checked属性。我们可以通过操作树数据的状态来实现,因为React是数据驱动视图的,修改数据后重新渲染即可。
1. 编写递归工具函数
首先需要几个递归函数来处理节点的修改:
// 递归设置当前节点及所有子节点的checked状态 const setChildrenChecked = (node, checked) => { node.checked = checked; if (node.children?.length) { node.children.forEach(child => setChildrenChecked(child, checked)); } }; // 向上遍历找到所有父节点并设置checked状态 const setParentChecked = (treeData, targetKey, checked) => { // 递归查找目标节点的所有父节点路径 const findParentPath = (data, key, parentNodes = []) => { for (const node of data) { if (node.key === key) return parentNodes; if (node.children?.length) { const result = findParentPath(node.children, key, [...parentNodes, node]); if (result) return result; } } return null; }; const parentNodes = findParentPath(treeData, targetKey); if (parentNodes) { parentNodes.forEach(node => node.checked = checked); } }; // 收集所有checked为true的节点key,用于更新checkedKeys状态 const collectCheckedKeys = (data) => { let keys = []; data.forEach(node => { if (node.checked) keys.push(node.key); if (node.children?.length) keys = keys.concat(collectCheckedKeys(node.children)); }); return keys; };
2. 完善onCheck方法
在onCheck方法里调用上面的工具函数,修改树数据并更新状态:
onCheck = (checkedKeys, event) => { const currentNode = event.node; const currentKey = currentNode.key; const isChecked = checkedKeys.includes(currentKey); // 判断是勾选还是取消勾选 // 深拷贝树数据,避免直接修改原state let newTreeData = JSON.parse(JSON.stringify(this.state.treeData)); // 找到当前节点在新数据中的引用 const findNode = (data, key) => { for (const node of data) { if (node.key === key) return node; if (node.children?.length) { const result = findNode(node.children, key); if (result) return result; } } return null; }; const targetNode = findNode(newTreeData, currentKey); if (targetNode) { // 设置当前节点及所有子节点的checked setChildrenChecked(targetNode, isChecked); // 设置所有父节点的checked setParentChecked(newTreeData, currentKey, isChecked); } // 收集所有已勾选的节点key const newCheckedKeys = collectCheckedKeys(newTreeData); // 更新状态,触发树的重新渲染 this.setState({ treeData: newTreeData, checkedKeys: newCheckedKeys }); };
三、补充说明
- 这里用
JSON.parse(JSON.stringify())深拷贝树数据,适合数据结构简单的场景;如果你的节点数据包含函数、Symbol等特殊类型,建议使用lodash.cloneDeep来深拷贝。 - rc-tree本身支持半选(indeterminate)状态,但你的需求是强制所有关联节点全选/取消,所以上面的逻辑会覆盖半选状态,直接设置所有节点的
checked为统一值。
内容的提问来源于stack exchange,提问作者Javier
相关产品推荐
相关产品推荐

