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

Ngrx Store状态不可变性疑问:修改数组内对象属性触发只读错误的原因

为什么NgRx中直接修改数组内对象属性会报错?

嘿,这个问题我之前在使用NgRx时也踩过坑,核心原因在于NgRx对状态不可变性的强制要求,以及你用扩展运算符做的是「浅拷贝」这件事,咱们一步步拆解:

1. NgRx状态的不可变性与冻结机制

NgRx的核心设计原则之一就是状态不可变——也就是说,你永远不能直接修改Store返回的状态对象,必须通过创建新的状态对象来更新。为了严格执行这个规则,很多情况下NgRx会用Object.freeze()冻结整个状态树,包括数组里的每个对象。

这就是为什么你直接修改newIngredients[index].amount会报错:虽然你拷贝了数组,但数组里的对象还是原状态的引用,修改它本质上就是在修改被冻结的原状态对象,自然会触发「只读」的错误提示。

而你用普通服务的时候没有这个问题,是因为服务里的状态是可变的,没有这种冻结和不可变的强制约束。

2. 扩展运算符的「浅拷贝」局限性

你写的newIngredients = [...state.ingredients]确实创建了一个新数组,但这只是浅拷贝:数组本身是新的,但数组里的每个ingredient对象还是和原数组里的对象共享同一个内存引用。

举个简单的例子:

const original = [{name: 'apple', amount: 1}];
const copied = [...original];
copied[0].amount = 2;
console.log(original[0].amount); // 输出2,因为两个数组里的对象是同一个引用

所以你以为是在修改新数组里的对象,其实还是碰了原状态的对象,这就违反了NgRx的规则。

3. 为什么「复制对象再替换」能生效?

当你复制那个同名对象(比如{...existingIngredient, amount: newAmount}),然后替换数组里的旧对象时,你实际上做了两件事:

  • 创建了一个全新的ingredient对象,完全脱离了原状态的引用
  • 用这个新对象替换新数组里的旧引用,整个新数组和原状态没有任何共享的引用

这种方式完全符合NgRx的不可变要求:原状态丝毫未动,你返回了一个全新的状态对象,NgRx能正确识别到状态变化并触发订阅更新。

正确的Reducer实现示例

这里给你一个符合NgRx规范的示例写法,供参考:

import { createReducer, on } from '@ngrx/store';
import { addIngredient } from './ingredients.actions';

interface Ingredient {
  name: string;
  amount: number;
}

interface IngredientsState {
  ingredients: Ingredient[];
}

const initialState: IngredientsState = {
  ingredients: []
};

export const ingredientsReducer = createReducer(
  initialState,
  on(addIngredient, (state, { payload }) => {
    const existingIngredientIndex = state.ingredients.findIndex(
      ing => ing.name === payload.name
    );

    if (existingIngredientIndex !== -1) {
      // 存在同名食材:创建新数组,替换对应位置为新对象
      return {
        ...state,
        ingredients: state.ingredients.map((ing, idx) =>
          idx === existingIngredientIndex
            ? { ...ing, amount: ing.amount + payload.amount }
            : ing
        )
      };
    } else {
      // 不存在同名食材:直接添加新对象到新数组
      return {
        ...state,
        ingredients: [...state.ingredients, payload]
      };
    }
  })
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 19:52:32