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

