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

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,
            },
          ]; 
        // 其他类型的处理逻辑
      }
    }

问题原因与解决方案

  1. 核心原因:equalityFn固定返回true导致缓存未更新
    当前equalityFn最后固定返回true,会让useSelector判定新旧状态无变化,因此不会更新内部缓存的旧值。后续触发状态更新时,oldVal依然是第一次缓存的初始状态。

  2. 修复步骤

    • 按照实际状态对比逻辑返回布尔值:如果新旧状态相等(无需重渲染)返回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就会是上一次的状态。
  3. 额外检查点

    • 确认rootReducer中liabilities对应的reducer没有在其他action类型中错误返回初始状态
    • 检查组件是否被意外卸载/重新挂载:组件重新挂载会导致useSelector重新初始化,oldVal回到初始状态

内容的提问来源于stack exchange,提问作者Sanish Joseph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:37:05