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

React购物车项目关闭模态框后useReducer执行两次的原因是什么?

问题根本原因

1. useReducer的reducer违反状态不可变原则

你在add_to_cart逻辑中直接修改了prevState.products_list内的元素属性,并且返回时直接复用了原数组引用:

// 错误写法:直接修改原状态的元素
prevState.products_list.forEach((element, index) => {
  if (element.id === action.id) {
    element.number++;
    element.allPrice += element.price;
  }
});
return {
  allAddedToCart: prevState.allAddedToCart + 1,
  products_list: prevState.products_list, // 直接返回原数组,引用无变化
};

如果你开启了React严格模式,useReducer的reducer函数会被故意调用两次来检测不可变规则合规性。因为你直接修改了原状态,两次调用的修改会叠加,最终表现为商品被添加两次。

2. Context的value未做稳定化处理

你直接在Context.Provider的value属性内定义函数和对象,每次ContextProvider重新渲染时,都会生成全新的value对象,所有消费该Context的组件都会被强制重新渲染:

  • 当open状态放在Header组件内时,打开/关闭模态框会触发Header重渲染,而Header消费了不稳定的Context,会进一步触发所有关联消费组件(比如商品列表项)的不必要重渲染,可能导致点击事件重复绑定,触发两次添加逻辑。
  • 当你把open移到Context内时,修改open只会触发ContextProvider重渲染,避免了Header层的重渲染链路,刚好规避了重渲染带来的事件重复绑定问题,但没有解决根本的不可变和Context不稳定的问题。

修复方案

第一步:修正reducer的不可变写法

修改add_to_cart逻辑,返回全新的状态对象和商品数组:

(state, action) => {
  if (action.type === "add_to_cart") {
    return {
      allAddedToCart: state.allAddedToCart + 1,
      // 生成新数组,修改目标元素时返回新对象,不改动原状态
      products_list: state.products_list.map(element => {
        if (element.id === action.id) {
          return {
            ...element,
            number: element.number + 1,
            allPrice: element.allPrice + element.price
          }
        }
        return element
      })
    };
  } else if (action.type === "initialize_list") {
    return {
      ...state,
      products_list: [...state.products_list, action.product],
    };
  }
  // 补充默认返回原状态
  return state
}

第二步:稳定Context的value

用useCallback包裹Context内的方法,用useMemo包裹整个value对象,仅在依赖变化时更新:

import React, { useReducer, useMemo, useCallback } from "react";

// ... Context定义部分保持不变

export const ContextProvider = (props) => {
  const [productsState, dispatchProducts] = useReducer(/* 上面修正后的reducer */, {
    allAddedToCart: 0,
    products_list: [],
  });

  // 用useCallback稳定函数引用
  const addToCart = useCallback((id) => {
    dispatchProducts({ type: "add_to_cart", id });
  }, []);

  const initializeProductList = useCallback((product) => {
    dispatchProducts({ type: "initialize_list", product });
  }, []);

  // 用useMemo稳定value对象
  const contextValue = useMemo(() => ({
    allAddedToCart: productsState.allAddedToCart,
    productsList: productsState.products_list,
    addToCart,
    initializeProductList,
  }), [productsState, addToCart, initializeProductList]);

  return (
    <Context.Provider value={contextValue}>
      {props.children}
    </Context.Provider>
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 18:45:04