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

深度克隆Redux状态后修改,为何开发环境不触发重渲染?

Redux开发环境下组件不触发重渲染(仅Next.js开发环境存在,生产正常)

Redux Slice 代码

export const mealPlanSlice = createSlice({
  name: "mealPlanner",
  initialState,
  reducers: {
    addOrUpdatePlan: addOrUpdate,
  }
})

export const { addOrUpdatePlan } = mealPlanSlice.actions;

export default mealPlanSlice.reducer;

对应的Reducer逻辑

import clone from "just-clone";

export const addOrUpdate = (
  state: IMealPlanState,
  action: PayloadAction<
    { date: DateString } & {
      components: {
        recipeId: RecipeUuid;
        componentId: ComponentUuid;
        servingsIncrease: number;
      }[];
    }
  >
) => {
  const newState = clone(state);

  const { date, components } = action.payload;
  for (const { recipeId, componentId, servingsIncrease } of components) {
    if (newState.plan[date]) {
      if (recipeId in newState.plan[date]) {
        const componentIndex = newState.plan[date][recipeId].findIndex(
          (mealPlanItem) => mealPlanItem.componentId === componentId
        );
        if (componentIndex > -1) {
          const component = newState.plan[date][recipeId][componentIndex];
          const newServings = component.servings + servingsIncrease;
          if (newServings > 0) {
            newState.plan[date][recipeId][componentIndex].servings =
              newServings;
          } else {
            newState.plan[date][recipeId].splice(componentIndex, 1);
            if (newState.plan[date][recipeId].length === 0) {
              delete newState.plan[date][recipeId];
            }
          }
        } else {
          newState.plan[date][recipeId].push({
            componentId,
            servings: servingsIncrease,
          });
        }
      } else {
        newState.plan[date][recipeId] = [
          {
            componentId,
            servings: servingsIncrease,
          },
        ];
      }
    }
  }

  return newState;
};

Store配置

export const store = configureStore<IFullStoreState>({
  reducer: {
    printing,
    plants,
    food,
    mealPlan,
    user,
  },
});

订阅状态的组件代码

const Recipes = () => {
  const mealPlan = useSelector((store) => store.mealPlan.plan);

  return ...
}

问题现象与排查信息

  • Redux DevTools显示状态变更正确,新老状态各层级均不浅相等,但分发action后组件不触发重渲染
  • 项目中多个Slice都存在此问题
  • 仅在Next.js开发构建环境出现,生产部署后问题消失

原因分析

  1. Next.js Fast Refresh机制冲突:开发环境的Fast Refresh会保留组件状态,可能与Redux的订阅逻辑兼容异常。当组件通过useSelector引用深层状态对象时,Fast Refresh可能未正确识别状态变更带来的组件依赖变化。
  2. 深度克隆工具的潜在兼容问题:just-clone在开发环境下可能与TypeScript类型或Next.js工具链存在兼容问题,导致克隆后的状态对象出现未预期的引用/结构问题(虽已确认状态差异,但仍需验证)。
  3. Redux DevTools开发增强的延迟:开发环境下Redux DevTools的增强器会对状态做额外处理,偶尔会导致状态变更通知延迟。

解决方案建议

  • 给useSelector添加自定义深比较函数,强制识别深层状态变化:
    import isEqual from 'lodash/isEqual';
    
    const mealPlan = useSelector((store) => store.mealPlan.plan, isEqual);
    
  • 替换just-clone为原生structuredClone,规避第三方库的兼容问题:
    const newState = structuredClone(state);
    
  • 临时禁用Fast Refresh测试,确认是否为其导致:
    // next.config.js
    module.exports = {
      fastRefresh: false,
    };
    
  • 检查Redux、React-Redux与Next.js的版本兼容性,避免版本不匹配引发的开发环境异常。

内容的提问来源于stack exchange,提问作者George Sheppard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:27:24