NgRx中修改state其他属性时activeLocations订阅触发问题排查
selectedOrder时getActiveLocations订阅被触发的原因? 我拥有locations和activeLocationsIds两个数组,locations存储所有位置对象,activeLocationsIds仅存储与locations ID一一对应的活跃位置ID。当前state结构如下:
export interface ICustomerHierarchyState { locations: ILocation[], activeLocationsIds: string[]; selectedOrder: number; }
获取活跃位置的选择器定义如下:
export const getActiveLocationsIds = createSelector( getCustomerHierarchyState, state => state.activeLocationsIds ) export const getActiveLocations = createSelector( getCustomerHierarchyState, getActiveLocationsIds , (state, getActiveLocationsIds ) => state.locations.filter(x => getActiveLocationsIds.includes(x.locationId)) ?? [] )
activeLocationsIds的reducer及对应action、selectedOrder的清除reducer分别为:
// activeLocationsIds reducer on(CustomerHierarchyPageActions.setActiveLocations, (state, action): ICustomerHierarchyState => { return { ...state, activeLocationsIds: action.locationsIds } }) // setActiveLocations action export const setActiveLocations = createAction( '[Customer-Hierarchy Page] Set ActiveLocations', props<{ locationsIds: string[] }>() ) // clearSelectedOrder reducer on(CustomerHierarchyPageActions.clearSelectedOrder, (state): ICustomerHierarchyState => { return { ...state, selectedOrder: -1 } })
我通过以下代码订阅getActiveLocations:
this.subscriptions.add( this.store.select(getActiveLocations) .subscribe( activeLocations => { this.activeLocations = activeLocations; } ));
问题:当我分发修改selectedOrder的action(如this.store.dispatch(CustomerHierarchyPageActions.clearSelectedOrder());)时,getActiveLocations的订阅会被触发,但该reducer仅修改了selectedOrder属性,请问这是什么原因?
核心原因
getActiveLocations选择器的第一个依赖是整个CustomerHierarchyState对象,而NgRx选择器的依赖对比是基于引用的。当你修改selectedOrder时,reducer通过...state解构返回了一个新的CustomerHierarchyState对象,此时选择器会判定第一个依赖发生了变化,进而重新执行计算函数,导致订阅被触发——哪怕locations和activeLocationsIds的实际内容完全没有改变。
解决方案
修改getActiveLocations选择器,让它只依赖真正需要的locations和activeLocationsIds两个状态片段,而非整个state:
- 先创建获取
locations的独立选择器:
export const getLocations = createSelector( getCustomerHierarchyState, state => state.locations )
- 调整
getActiveLocations选择器,仅依赖getLocations和getActiveLocationsIds:
export const getActiveLocations = createSelector( getLocations, getActiveLocationsIds, (locations, activeIds) => locations.filter(x => activeIds.includes(x.locationId)) ?? [] )
修改后,只有当locations或activeLocationsIds的引用发生变化时,getActiveLocations才会重新计算并触发订阅,修改selectedOrder时就不会再触发不必要的订阅了。
内容的提问来源于stack exchange,提问作者Domagoj Hamzic

