为什么NgRx不直接修改store状态?reducer返回新状态有什么规范?
为什么不能直接修改Store状态,必须通过Reducer返回新副本
- 保证状态变化可追溯、可调试:所有状态修改都必须对应明确的Action,集中在Reducer中处理,你可以通过NgRx DevTools完整回溯每一次状态变化的触发原因、变化前后的值,甚至可以回放操作流程。如果允许直接修改原状态,状态变化会散落在应用各个角落,出问题时根本没法定位是哪里触发了异常修改。
- 保证响应式更新正常工作:NgRx的状态订阅、selector选择器都是通过引用对比判断状态是否发生变化的,如果你直接修改原状态,对象引用不会变化,NgRx就会认为状态没有更新,依赖该状态的组件、effect都不会触发更新,出现视图不同步的问题。返回新副本会生成新的引用,就能正确触发所有依赖该状态的更新逻辑。
- 避免副作用与竞态问题:Reducer是纯函数,相同的state和action输入永远会得到相同的输出,没有额外副作用。所有状态更新的顺序完全由Action派发顺序决定,不会出现多个地方同时修改状态导致的不可预期的竞态问题,保证了状态的一致性。
返回新状态的规则
有通用的核心规范必须遵守,具体写法可以根据状态结构灵活调整:
必须遵守的通用规范
- 永远不直接修改传入的原state对象,不管是顶层state还是嵌套的子属性,都禁止直接赋值、调用会修改原对象/数组的方法(比如数组的
push/splice/pop、对象直接改属性值) - 只需要对发生修改的属性路径上的所有层级做浅复制,未修改的属性直接复用原有引用即可,不需要做全量深复制(会浪费性能)
- 修改完成后必须返回完整的新状态对象,不能只返回修改的部分属性
不同结构的灵活调整方式
- 像你示例中修改顶层数组属性的场景,只需要复制顶层state,再复制要修改的ingredients数组追加新值即可,和你写的示例代码完全一致
- 如果是修改深层嵌套的属性,就需要逐层复制到修改的属性层级,比如修改state下user对象的address属性的city字段,写法如下:
return { ...state, // 复制顶层state user: { ...state.user, // 复制user对象 address: { ...state.user.address, // 复制address对象 city: action.payload.newCity // 仅修改目标属性 } } }
- 如果状态嵌套层级过深,逐层写展开运算符过于繁琐,也可以用Immer库简化不可变更新的写法,直接写修改原对象的语法,Immer底层会自动帮你生成新的状态副本。
内容的提问来源于stack exchange,提问作者derstauner
相关产品推荐
相关产品推荐

