You need to enable JavaScript to run this app. Immer结合NGXS使用时对象仍冻结致属性无法赋值的问题
优惠活动
大模型
产品
解决方案
定价
更多

Immer结合NGXS使用时对象仍冻结致属性无法赋值的问题

报错核心根因

你遇到的Cannot assign to read only property报错,本质是三个机制错位叠加导致的,不属于单纯的Immer调用逻辑问题:

  • NGXS 3.8+ 版本默认对全局Store中的所有状态对象执行深度冻结,嵌套的user.savedPrograms属性会被打上只读标记,任何直接修改原状态的操作都会触发JS层面的只读保护报错。
  • @ImmutableContext()装饰器并非NGXS核心框架自带的能力,它是Immer适配插件提供的桥接逻辑,本身没有直接解除只读冻结的能力,两个前置条件不满足时,这个装饰器加了等于没加:
    1. 没有在NGXS根模块全局注册Immer插件,装饰器找不到对应的桥接执行逻辑,运行时就是没有任何逻辑的空函数
    2. 装饰器的执行顺序不对:TS中叠在方法上的装饰器,越靠近方法签名的越先执行包装逻辑。你之前把@Action放在装饰器栈顶层、@ImmutableContext放在下方,相当于先执行@Action的逻辑,把已经被深度冻结的原状态塞给上下文,再执行@ImmutableContext,这时候状态已经传入完成,根本来不及修改只读属性标记。
  • 你手动调用produce传入的draft,本质是被NGXS深度冻结的原状态对象,Immer生成代理的时候不会强行给原对象解除只读标记,尝试给嵌套属性赋值的时候自然会触发报错。
正确的落地实现步骤
  • 第一步:全局注册Immer插件。在NGXS根模块的配置中,导入withImmer扩展能力,不要只在单个动作里手动引入produce方法。
  • 第二步:调整装饰器顺序,将@ImmutableContext()放置在比@Action更靠近方法签名的位置,保证先完成状态的只读标记解绑、生成可修改的Immer代理,再挂载动作监听逻辑。
  • 第三步:移除动作内手动调用produce的冗余代码,插件会自动将状态上下文的入参转换为Immer draft,直接在setState的回调中修改属性即可,不需要手动写深拷贝逻辑。

修正后的动作实现参考:

@Action(UserActions.AddProgram)
@ImmutableContext()
public addProgram(ctx: StateContext<UserStateModel>, action: AddProgramAction) {
  ctx.setState((draft: UserStateModel) => {
    draft.user.savedPrograms.push(action.payload);
  });
}
现有临时方案的问题

你目前用JSON序列化反序列化做深拷贝的实现,本质是靠生成一个完全没有只读标记的全新普通对象绕开报错,这种实现存在两个明显缺陷:

  • 性能损耗远高于插件原生的Immer代理机制,每次状态更新都要对整个状态树做全量序列化/反序列化,状态规模变大后会出现明显的渲染卡顿
  • 无法处理非可序列化字段:如果状态中包含函数、Map、Set、循环引用结构,JSON序列化过程会直接丢失这些字段的内容,出现难以排查的业务bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 07:36:03