JS结合Immer/Redux向深层嵌套节点赋值对象报错如何解决
报错核心原因
你遇到的是典型的浅拷贝+深度冻结对象赋值报错,和上层节点是否存在无关:
- Immer 默认会对所有生产出来的 Redux 状态做递归
Object.freeze冻结,防止意外修改,state.getProgramTicketsRes本身就是被深度冻结的只读对象。 - 你写的
{ ...current(state.getProgramTicketsRes) }属于浅拷贝:展开运算符只会复制对象第一层的属性,第一层属性指向的深层嵌套对象(包括data、layout、sectors、rowComponents、bookingStates等)依然是原来被Immer冻结的只读对象。 - 当你尝试给深层路径下的
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
相关产品推荐
相关产品推荐

