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

JS结合Immer/Redux向深层嵌套节点赋值对象报错如何解决

报错核心原因

你遇到的是典型的浅拷贝+深度冻结对象赋值报错,和上层节点是否存在无关:

  1. Immer 默认会对所有生产出来的 Redux 状态做递归Object.freeze冻结,防止意外修改,state.getProgramTicketsRes本身就是被深度冻结的只读对象。
  2. 你写的{ ...current(state.getProgramTicketsRes) }属于浅拷贝:展开运算符只会复制对象第一层的属性,第一层属性指向的深层嵌套对象(包括data、layout、sectors、rowComponents、bookingStates等)依然是原来被Immer冻结的只读对象。
  3. 当你尝试给深层路径下的bookingStates[eventTimeId]赋值时,实际是在修改被冻结的只读对象,直接触发JS运行时的只读赋值错误。
正确实现方案

根据你的使用场景选一种即可,不要混用浅拷贝操作:

  • 方案1:直接在Immer的更新上下文(比如createSlice的reducer、produce回调)内操作draft,这是Redux Toolkit推荐的标准写法,不需要手动做任何拷贝:
// 示例为createSlice内的reducer写法
reducers: {
  setBookingState(state, action) {
    const { sectorName, rowNum, rowComponentNum, eventTimeId } = action.payload
    // 直接顺着draft路径赋值,Immer会自动处理不可变更新,不会触发冻结报错
    state.getProgramTicketsRes.data.layout.sectors[sectorName][rowNum]
      .rowComponents[rowComponentNum].bookingStates[eventTimeId] = {}
  }
}
  • 方案2:如果你确实需要先取出状态快照再修改,必须先完整解包所有冻结的节点,不要只做第一层浅拷贝:
import { current } from '@reduxjs/toolkit' // 或从immer导入

// 先拿到完整的、未冻结的普通对象快照,不要加外层的展开运算符
const ticketResSnapshot = current(state.getProgramTicketsRes)
const { sectorName, rowNum, rowComponentNum, eventTimeId } = action.payload
// 对普通快照做深层赋值,此时没有冻结限制
ticketResSnapshot.data.layout.sectors[sectorName][rowNum]
  .rowComponents[rowComponentNum].bookingStates[eventTimeId] = {}
// 最后把修改完成的快照整体赋值回状态
state.getProgramTicketsRes = ticketResSnapshot

注意:如果是在Immer上下文外操作store状态,必须使用深拷贝(比如用structuredClone或者Immer的produce包一层),不能用第一层展开的浅拷贝修改深层属性。

内容的提问来源于stack exchange,提问作者János

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 21:01:02