You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用React Hooks维护层级嵌套对象状态(新增/更新实现)

React四层嵌套书籍状态更新实现方案

问题根源

你写的新增小节代码报错,核心原因是old.chapters是章节对象构成的数组,不是单个章节实例,直接访问数组的.sections属性自然会拿到undefined,触发语法/逻辑错误。
React嵌套状态更新的核心规则是每一层级都要做浅拷贝,仅修改目标节点,其余未改动节点原样返回。不管是直接在useState的更新函数里写逻辑,还是用useReducer抽离逻辑,本质都是遵守这个不可变更新的规则,reducer本身只是把分散的更新逻辑收拢统一管理,不会改变更新的底层逻辑。

各层级操作的可运行实现

你的场景每次仅操作单一层级的单个对象,完全可以沿用之前实现章节增改的嵌套map思路,不需要额外引入复杂依赖。

新增小节(Section)

前提是已经拿到待新增小节所属的目标章节IDtargetChapterId,以及构造完成的newSection对象:

setSelectedBook(old => {
  return {
    ...old,
    // 第一层遍历所有章节,匹配目标章节
    chapters: old.chapters.map(chapter => {
      // 非目标章节直接原样返回,不做修改
      if (chapter.id !== targetChapterId) return chapter;
      // 匹配到目标章节,浅拷贝后更新sections数组
      return {
        ...chapter,
        sections: [...chapter.sections, newSection]
      }
    })
  }
})

更新小节(Section)

比更新章节多一层遍历逻辑,需要传入目标章节IDtargetChapterId、待更新小节IDtargetSectionId、更新后的小节数据updatedSection:

setSelectedBook(old => {
  return {
    ...old,
    chapters: old.chapters.map(chapter => {
      if (chapter.id !== targetChapterId) return chapter;
      return {
        ...chapter,
        // 第二层遍历当前章节下的所有小节,匹配目标
        sections: chapter.sections.map(section => {
          if (section.id !== targetSectionId) return section;
          // 匹配到目标小节,返回更新后的内容
          return {...section, ...updatedSection};
        })
      }
    })
  }
})

新增/更新条目(Item)

按照相同的嵌套逻辑再加一层遍历即可,以新增条目为例,需要传入所属章节IDtargetChapterId、所属小节IDtargetSectionId、新条目对象newItem:

setSelectedBook(old => {
  return {
    ...old,
    chapters: old.chapters.map(chapter => {
      if (chapter.id !== targetChapterId) return chapter;
      return {
        ...chapter,
        sections: chapter.sections.map(section => {
          if (section.id !== targetSectionId) return section;
          return {
            ...section,
            items: [...section.items, newItem]
          }
        })
      }
    })
  }
})

更新条目逻辑同理,在小节遍历的内部再加一层items数组的map,匹配到目标条目ID时返回更新后的对象即可。

useReducer适用场景

如果后续操作类型增多(比如删除节点、节点排序、批量修改等),可以用useReducer把所有更新逻辑收拢到统一位置管理,避免更新逻辑散落在各个组件的事件回调里,可维护性更高。基础reducer结构参考如下:

function bookReducer(state, action) {
  switch(action.type) {
    case 'ADD_CHAPTER':
      return {...state, chapters: [...state.chapters, action.payload]};
    case 'UPDATE_CHAPTER':
      return {
        ...state,
        chapters: state.chapters.map(ch => ch.id === action.payload.id ? {...ch, ...action.payload.data} : ch)
      };
    case 'ADD_SECTION':
      return {
        ...state,
        chapters: state.chapters.map(ch => {
          if (ch.id !== action.payload.chapterId) return ch;
          return {...ch, sections: [...ch.sections, action.payload.section]}
        })
      }
    // 其余更新小节、增删改条目的逻辑可以按相同模式补充case
    default:
      return state;
  }
}

// 组件中初始化
const [selectedBook, dispatch] = useReducer(bookReducer, initialBookState);
// 触发新增小节操作时直接dispatch即可
dispatch({
  type: 'ADD_SECTION',
  payload: {
    chapterId: selectedChapterId,
    section: newSection
  }
})

实操提示

  • 你当前的四层嵌套深度不算高,手动写嵌套map的写法完全可维护,不需要额外引入工具库。如果后续嵌套层级进一步加深、或者增删改查逻辑变复杂,再考虑用Immer这类工具简化手写浅拷贝的重复代码即可。
  • 每次调用后端接口保存完单个对象后,直接用上面的更新逻辑匹配对应ID替换旧节点即可,不需要每次都重新请求整本书的全量数据,减少接口开销。
  • 遍历过程中非目标节点一定要原样返回,不要做无意义的拷贝,避免触发不必要的组件重渲染。

内容的提问来源于stack exchange,提问作者MattoMK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:15:30