React-Redux useSelector自定义equalityFn中oldVal始终为初始状态问题
React-Redux useSelector 自定义equalityFn时oldVal始终为初始状态的问题
使用React-Redux的useSelector获取状态中的对象数组,通过自定义equalityFn避免不必要重渲染。但dispatch动作后,store已正确更新,newVal能拿到新状态,oldVal却始终是reducer的初始状态,而非上一次的状态。
已确认的情况:
- 未直接修改状态,开发环境启用
reduxImmutableStateInvariant中间件防止状态篡改 - 添加状态的逻辑是返回新数组(代码可见)
- 相同的
equalityFn用于简单对象状态时,oldVal和newVal表现正常,怀疑问题与数组类型状态有关
组件中useSelector代码
const liabilities = useSelector((state: any) => state.liabilities, { equalityFn: (oldVal, newVal) => { console.log("oldVal is always the initial state"); console.log("newVal is always the updated state"); // 检查新旧状态差异的逻辑 if ( oldVal.some( (ol: ILiabilities) => ol.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) && !newVal.some( (nl: ILiabilities) => nl.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) ) { // 移除场景 console.log("Remove"); } else if ( !oldVal.some( (ol: ILiabilities) => ol.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) && newVal.some( (nl: ILiabilities) => nl.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) ) { // 添加场景 debugger; console.log("Add"); } else if ( oldVal.some( (ol: ILiabilities) => ol.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) && newVal.some( (nl: ILiabilities) => nl.liabilitiesType === LiabilitiesTypeEnum.charitableGifts ) ) { // 更新场景 console.log("Update"); } // 暂时返回true return true; }, });
Store配置代码
const store = configureStore({ reducer: rootReducer, preloadedState, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat( process.env.NODE_ENV === "development" ? [thunk as ThunkMiddleware, reduxImmutableStateInvariant()] : [thunk as ThunkMiddleware] ), });
Reducer添加状态代码片段
case types.ADD_LIABILITIES: { switch (action.liabilitiesType) { case LiabilitiesTypeEnum.probate: return [ ...state, { ...defaultLiabilities, liabilitiesType: LiabilitiesTypeEnum.lastExpenses, }, ]; // 其他类型的处理逻辑 } }
问题原因与解决方案
核心原因:equalityFn固定返回true导致缓存未更新
当前equalityFn最后固定返回true,会让useSelector判定新旧状态无变化,因此不会更新内部缓存的旧值。后续触发状态更新时,oldVal依然是第一次缓存的初始状态。修复步骤
- 按照实际状态对比逻辑返回布尔值:如果新旧状态相等(无需重渲染)返回
true,否则返回false。 - 示例(仅关心
charitableGifts类型项的存在与否):equalityFn: (oldVal, newVal) => { const oldHasCharitable = oldVal.some(ol => ol.liabilitiesType === LiabilitiesTypeEnum.charitableGifts); const newHasCharitable = newVal.some(nl => nl.liabilitiesType === LiabilitiesTypeEnum.charitableGifts); // 状态一致则返回true(不重渲染),否则返回false(需要重渲染) return oldHasCharitable === newHasCharitable; } - 注意:当
equalityFn返回false时,useSelector会更新缓存的旧值为当前新状态,下次对比时oldVal就会是上一次的状态。
- 按照实际状态对比逻辑返回布尔值:如果新旧状态相等(无需重渲染)返回
额外检查点
- 确认
rootReducer中liabilities对应的reducer没有在其他action类型中错误返回初始状态 - 检查组件是否被意外卸载/重新挂载:组件重新挂载会导致
useSelector重新初始化,oldVal回到初始状态
- 确认
内容的提问来源于stack exchange,提问作者Sanish Joseph
相关产品推荐
相关产品推荐

