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

NgRx规范化状态下添加子对象的实现问题求助

问题描述

我正在开发Angular前端的businessPartnerEditor功能,从后端获取嵌套的businessPartnerComplete对象:

// 从后端获取的简化对象结构
{
  id: number,
  globalId: string,
  // 其他字段...
  addressList: [Address]
}
// Address对象结构
{
  id: number,
  globalId: string,
  // 其他字段...
  toTheAttentionOf: [ToTheAttentionOf]
}

我使用normalizr对该对象进行规范化,并通过entity adapter构建状态,得到三个FeatureState:BusinessPartner、Address和ToTheAttentionOf。其中AddressFeatureState结构如下:

// AddressFeatureState结构
{
  ids: [string],
  entities: Dictionary<AddressNormalized>,
  selectedAddressId: string
}
// AddressNormalized结构
{
  id: number,
  globalId: string,
  // 其他字段...
  toTheAttentionOf: [string] // 存储关联的ToTheAttentionOf对象的globalId数组
}

现在需要实现用户为指定地址创建新ToTheAttentionOf对象的功能:先创建新对象并从后端获取globalId,通过entity adapter的upsert更新ToTheAttentionOfFeatureState(这部分已正常工作),但还需要将新对象的globalId添加到对应Address对象的toTheAttentionOf数组中。

我编写的reducer代码如下:

export const AddressReducer = createReducer(
  on(addAttentionOf, (state, {toTheAttentionOf}) => {
    let entities = adapter.getSelectors().selectEntities(state);
    entities[state.selectedId].toTheAttentionOfDtos.push(toTheAttentionOf.globalId);
    return {...state, entities};
  })
)

执行时抛出TypeError: Cannot add property 1, object is not extensible错误,我认为这是因为selectEntities(state)返回的是状态的一部分,而ngrx状态是不可变的,无法直接修改。

请问最佳解决方式是什么?是否必须深拷贝?这是常见场景,但实现起来似乎过于繁琐,我的状态设计是否存在问题?

解决方案

1. 正确的不可变更新方式

你不需要深拷贝整个状态,只需要拷贝需要修改的单个实体和数组即可。利用ngrx entity adapter提供的upsertOne方法,它会自动处理状态的不可变性,无需手动复制整个entities对象:

export const AddressReducer = createReducer(
  on(addAttentionOf, (state, { toTheAttentionOf }) => {
    // 获取当前选中的地址实体
    const selectedAddress = state.entities[state.selectedAddressId];
    // 边界判断:如果选中地址不存在,直接返回原状态
    if (!selectedAddress) return state;

    // 1. 复制原地址实体,避免修改原状态
    // 2. 复制原toTheAttentionOf数组并添加新的globalId,保证数组不可变
    const updatedAddress = {
      ...selectedAddress,
      toTheAttentionOf: [...selectedAddress.toTheAttentionOf, toTheAttentionOf.globalId]
    };

    // 使用entity adapter的upsertOne方法更新状态,自动维护不可变性
    return adapter.upsertOne(updatedAddress, state);
  })
);

2. 为什么之前的代码报错

ngrx的状态默认是冻结(不可扩展)的,直接调用push修改原状态中的数组会违反不可变原则,因此抛出错误。上述方案通过浅拷贝实体+数组的方式,既保证了不可变性,又避免了不必要的深拷贝操作。

3. 状态设计合理性

你的状态设计是完全合理的:使用normalizr扁平化嵌套数据,结合ngrx entity adapter管理实体状态,是处理关联数据的标准方案。这种设计能避免深层嵌套状态带来的更新复杂度,同时提升状态查询的效率。当前的场景只是关联实体的常规更新操作,不需要调整状态结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 04:45:12