React递归函数组件中如何实现递归状态的CRUD操作
递归嵌套状态的CRUD实现方案(无第三方库)
针对你的递归嵌套状态结构,核心思路是通过路径索引链定位节点,在顶层组件维护状态并提供操作回调,子组件通过传递路径和调用回调来触发状态更新(严格遵循React immutable更新原则)。以下是完整实现代码和关键说明:
完整修改后的代码
const initialData = { op: 0, values: [ { op: 0, value: { values: [ { op: 2, value: { values: [] } } ] } }, { op: 1, value: { values: [] } } ] }; // 工具函数:根据路径获取嵌套节点的深层拷贝并修改 const updateNestedNode = (obj, path, updater) => { if (path.length === 0) return updater(obj); const [key, ...restPath] = path; return { ...obj, values: obj.values.map((item, idx) => { if (idx === key) { return updateNestedNode(item, restPath, updater); } return item; }) }; }; function App3() { const [mainState, setMainState] = React.useState(initialData); // 更新指定节点的op值 const handleUpdateOp = (path, newOp) => { setMainState(prev => updateNestedNode(prev, path, node => ({ ...node, op: parseInt(newOp) || 0 }))); }; // 给指定节点添加子节点 const handleAddChild = (path) => { const newChild = { op: 0, value: { values: [] } }; setMainState(prev => updateNestedNode(prev, path, node => ({ ...node, value: { ...node.value, values: [...(node.value?.values || []), newChild] } }))); }; // 删除指定节点 const handleDeleteNode = (path) => { if (path.length === 0) return; // 根节点不允许删除 const parentPath = path.slice(0, -1); const nodeIndex = path[path.length - 1]; setMainState(prev => updateNestedNode(prev, parentPath, parentNode => ({ ...parentNode, value: { ...parentNode.value, values: parentNode.value.values.filter((_, idx) => idx !== nodeIndex) } }))); }; // 添加根层级节点 const handleAddRootNode = () => { setMainState(prev => ({ ...prev, values: [...prev.values, { op: 0, value: { values: [] } }] })); }; return ( <div> {mainState.values.map((x, i) => ( <RecursiveFn key={i} value={x} path={[i]} onUpdateOp={handleUpdateOp} onAddChild={handleAddChild} onDeleteNode={handleDeleteNode} /> ))} <button onClick={handleAddRootNode}>添加根节点</button> <hr /> <div> <pre>{JSON.stringify(mainState, null, 2)}</pre> </div> </div> ); } function RecursiveFn({ value, path, onUpdateOp, onAddChild, onDeleteNode }) { return ( <div style={{ padding: 5, margin: 10, paddingLeft: 20, border: '1px solid black' }}> <div> OP - <input type="number" value={value.op} onChange={(e) => onUpdateOp(path, e.target.value)} /> </div> {value.value?.values?.map((child, idx) => ( <RecursiveFn key={idx} value={child} path={[...path, idx]} onUpdateOp={onUpdateOp} onAddChild={onAddChild} onDeleteNode={onDeleteNode} /> ))} <button onClick={() => onAddChild(path)}>添加子节点</button> <button onClick={() => onDeleteNode(path)}>删除当前节点</button> </div> ); } ReactDOM.render(<App3 />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script> <div id="root"></div>
关键实现说明
路径索引链(path)
- 每个递归节点都会接收一个
path数组,记录从根节点到当前节点的索引路径(比如根下第0个节点的子节点第1个,path就是[0,1]) - 子节点传递给下一层时,会把当前索引追加到path中,确保能精准定位任意层级的节点
- 每个递归节点都会接收一个
Immutable状态更新
- 用
updateNestedNode工具函数递归复制嵌套对象,只修改目标节点,避免直接修改原state导致React无法检测更新 - 所有状态更新都返回新的对象/数组,严格遵循React的state更新规则
- 用
CRUD操作拆解
- 更新(Update):通过path找到目标节点,修改
op值并返回新对象 - 新增(Create):根节点新增直接操作顶层
values数组;子节点新增则找到目标节点的value.values数组,追加新节点 - 删除(Delete):通过path获取父节点路径和当前节点索引,过滤父节点的
values数组移除目标节点 - 查询(Read):直接通过state传递给子组件渲染,无需额外处理
- 更新(Update):通过path找到目标节点,修改
内容的提问来源于stack exchange,提问作者Gautam Parmar
相关产品推荐
相关产品推荐

