如何使用递归函数动态更新React中深层嵌套对象的state?
问题解答
为什么第一次点击按钮时数值加1,后续再点击就会每次加2?
你现有代码存在三个核心错误导致该现象:
- 违反了React状态不可变原则:你在更新函数中直接写
prevState[1].b = prevState[1].b + 1,直接修改了旧状态对象本身,没有生成新的状态副本。 - React18+开发环境默认开启严格模式,状态更新函数会被调用两次用于排查不可变相关问题。因为你直接修改了原状态,第一次调用已经把旧状态的b值加了1,第二次调用拿到的是已经被修改的旧状态,就会再加一次,最终每次点击加2。
- 类型错误:初始state是数组,但你更新时返回的是
{ ...prevState },把数组转成了普通对象,第一次更新逻辑和后续不统一,所以第一次点击只加1。
如何动态更新每个嵌套的state?
实现思路是路径追踪+不可变更新:
- 每个递归组件携带当前节点在整个嵌套结构中的路径,比如顶层的
b=1路径是[1, 'b'],最深层的b=4路径是[0, 'a', 0, 'a', 0, 'a', 1, 'b'] - 点击按钮时根据路径对状态做不可变的深层更新,这里推荐用
immer库简化深层不可变更新代码,不用写复杂的逐层拷贝逻辑。 - 重构渲染逻辑,遍历所有数组元素,判断有
b属性就渲染按钮,有a属性就传入对应路径递归渲染。
完整实现代码
首先安装immer:
npm install immer
功能代码如下:
import { useState } from 'react'; import { produce } from 'immer'; const initialState = [ { a: [{ a: [{ a: [{ a: 5 }, { b: 4 }] }, { b: 3 }] }, { b: 2 }] }, { b: 1 }, ]; // 递归组件 function RecursiveComponent({ data, path, setNestedObject }) { return ( <div style={{ paddingLeft: 20 }}> {data.map((item, index) => { // 渲染b属性对应的按钮 if ('b' in item) { const currentPath = [...path, index, 'b']; return ( <button key={currentPath.join('-')} onClick={() => { setNestedObject(prev => produce(prev, draft => { // 根据路径修改对应值 let target = draft; for (let i = 0; i < currentPath.length - 1; i++) { target = target[currentPath[i]]; } target[currentPath.at(-1)] += 1; })) }} style={{ margin: '4px' }} > b: {item.b} </button> ) } // 递归渲染a属性的嵌套数组 if ('a' in item) { return ( <RecursiveComponent key={index} data={item.a} path={[...path, index, 'a']} setNestedObject={setNestedObject} /> ) } return null; })} </div> ) } function App() { const [nestedObject, setNestedObject] = useState(initialState); return ( <div style={{ padding: 20 }}> <RecursiveComponent data={nestedObject} path={[]} setNestedObject={setNestedObject} /> </div> ); } export default App;
如果不想用immer,也可以自己实现深层不可变更新,只是代码会更繁琐,需要逐层拷贝嵌套的对象和数组。
内容的提问来源于stack exchange,提问作者pepinillo6969
相关产品推荐
相关产品推荐

