NGRX最佳实践:如何避免状态属性被重复值更新?
NGRX中重复触发视图更新的相等性检查责任划分与最佳实践
问题场景回顾
定义状态接口:
interface State { toto: string[] }
触发动作:setToto(["value1", "value2"])
Reducer返回新状态:
{...state, toto: ["value1", "value2"]}
Selector定义:
const selectToto = () => createSelector(selectRootState, state => state.toto);
视图通过selectToto() | async订阅数据时,重复触发相同值的setToto动作,会导致视图重复更新,这是需要避免的。
你当前在Reducer中用Lodash的isEqual做深相等检查来规避这个问题,但不确定这是否是最优方案,下面针对「相等性检查该由Selector/Reducer/组件谁负责」的问题,结合NGRX最佳实践分析:
各层级的职责与可行性分析
1. Reducer层(推荐作为首要处理点)
Reducer的核心职责是根据动作返回新的不可变状态,或原状态。从状态变更的源头控制不必要的更新,是最合理的选择:
- 当动作携带的 payload 与当前状态中的数据内容完全一致时,直接返回原状态引用,避免创建新的状态对象/数组。
- 这样后续的Selector、组件订阅都会因为状态引用未变更(浅对比即可识别),不会触发更新。
- 你当前的实现逻辑是符合Reducer职责的,但可以根据数据复杂度优化对比逻辑:
- 如果是简单数组(如
string[]),可以先对比长度,再逐个元素对比,比isEqual更高效:case Action.SetToto: const current = state.toto; const payload = action.payload; if (current.length === payload.length && current.every((val, idx) => val === payload[idx])) { return state; } return { ...state, toto: payload }; - 对于复杂对象,再考虑使用
isEqual这类深对比工具,但要注意大数据量下的性能开销。
- 如果是简单数组(如
2. Selector层(推荐作为兜底补充)
NGRX的Selector自带**记忆化(Memoization)**功能,默认通过浅对比判断输入是否变化。如果Reducer因为某些原因返回了内容相同但引用不同的状态(比如第三方库生成的新对象),可以通过自定义Selector的相等性检查来拦截重复值:
- 使用
createSelector的第三个参数,配置resultEqualityCheck为深对比函数:import { createSelector } from '@ngrx/store'; import isEqual from 'lodash/isEqual'; export const selectToto = createSelector( selectRootState, (state) => state.toto, { memoizeOptions: { resultEqualityCheck: isEqual } } ); - 这样即使状态引用变更,只要内容相同,Selector不会重新发射新值,视图就不会触发更新。
- 注意:Selector的记忆化是基于输入参数的浅对比,如果根状态引用发生变化(即使
toto未变),默认还是会重新计算,所以配合Reducer的检查能获得最优效果。
3. 组件层(不推荐)
组件的职责是展示数据、处理用户交互,将状态相等性检查放在组件层会带来以下问题:
- 打破职责单一原则,组件逻辑变得臃肿,难以维护。
- 需要在组件中手动过滤重复值,比如在订阅时添加
distinctUntilChanged(isEqual),增加了组件的复杂度。 - 不符合NGRX「状态逻辑集中管理」的设计理念,状态相关逻辑应该统一放在Store层(Reducer/Selector)。
总结最佳实践
- 优先在Reducer层处理:从源头避免不必要的状态引用变更,减少后续所有订阅的无效更新。
- 配合Selector层兜底:针对Reducer无法覆盖的场景(如第三方数据生成的新引用),用自定义记忆化相等检查拦截重复值。
- 绝对避免组件层处理:保持组件简洁,专注于UI展示和交互逻辑。
- 性能优化补充:如果处理复杂大对象,可使用Immer库来自动实现结构共享——Immer在修改状态时,会复用未变化的部分引用,这样浅对比就能识别内容未变更,无需深对比。
内容的提问来源于stack exchange,提问作者The Segfault
相关产品推荐
相关产品推荐

