React useReducer触发一次却四次添加订单状态问题排查与修复
咖啡配送应用订单历史模块问题:一次性添加四条相同订单
我正在开发一款包含购物车、支付表单和订单历史的咖啡配送应用,当前遇到订单历史模块的问题:完成订单时,本应将购物车商品与表单信息整合后添加至订单历史,但触发添加订单数组的函数时,代码会一次性创建四条完全相同的订单。
相关代码如下:
Reducer与状态定义
const reducer = (state: any, action: Actions) => { if (action.type === 'COMPLETE_ORDER') { const newOrdersList: OrderType[] = state.orders newOrdersList.push({ orderItems: action.payload.orderItems, paymentPreference: action.payload.paymentPreference, totalPrice: action.payload.totalPrice, date: action.payload.date, id: action.payload.id }) console.log(newOrdersList) // (4) [{…}, {…}, {…}, {…}] return { orders: newOrdersList, cartItems: [] } } return state }; const [ orderState, dispatch ] = useReducer(reducer, { cartItems: [], orders: [] }); const completeOrder = (newOrderData: OrderType) => { dispatch({ type: 'COMPLETE_ORDER', payload: { ...newOrderData } }) };
提交订单的Effect
useEffect(() => { const totalPrice = cartItems.reduce( (total, currentValue) => total = total + currentValue.price!, 0 ) const orderItems = cartItems const newOrderData: OrderType = { orderItems, paymentPreference, totalPrice, date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }), id: uuid() } completeOrder(newOrderData) }, []);
问题原因
- 状态突变违反React不可变原则:Reducer中
const newOrdersList = state.orders是直接引用原状态数组,调用push会直接修改原state.orders,导致React无法正确追踪状态变化,可能重复触发状态更新,多次添加相同订单。 - useEffect依赖项缺失:Effect中用到了
cartItems和paymentPreference但未添加到依赖数组,若组件因其他原因重复渲染,会导致Effect重复执行;加上React严格模式下开发环境会执行两次Effect,进一步加剧重复提交。 - 自动触发逻辑不合理:用Effect自动提交订单,而非用户手动操作触发,容易因组件生命周期或状态变化导致多次调用
completeOrder。
修复方案
1. 修复Reducer,保证状态不可变
在Reducer中创建原数组的浅拷贝,避免直接修改原状态:
const reducer = (state: any, action: Actions) => { if (action.type === 'COMPLETE_ORDER') { // 浅拷贝原订单数组,避免修改原状态 const newOrdersList: OrderType[] = [...state.orders]; newOrdersList.push({ orderItems: action.payload.orderItems, paymentPreference: action.payload.paymentPreference, totalPrice: action.payload.totalPrice, date: action.payload.date, id: action.payload.id }); console.log(newOrdersList); // 返回全新的状态对象,保留原状态的其他属性 return { ...state, orders: newOrdersList, cartItems: [] }; } return state; };
2. 修正Effect逻辑(或改用手动触发)
方案A:调整Effect的依赖与触发条件
如果仍需用Effect触发,需补充依赖项并添加防重复提交判断:
useEffect(() => { // 购物车为空时不提交订单 if (cartItems.length === 0) return; const totalPrice = cartItems.reduce( (total, currentValue) => total + (currentValue.price || 0), 0 ); // 拷贝购物车商品,避免后续状态变化影响订单数据 const orderItems = [...cartItems]; const newOrderData: OrderType = { orderItems, paymentPreference, totalPrice, date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }), id: uuid() }; completeOrder(newOrderData); }, [cartItems, paymentPreference]); // 添加所有用到的外部依赖
方案B:改用用户手动触发(推荐)
将订单提交逻辑移到按钮点击事件中,避免自动重复触发:
// 订单提交处理函数 const handleSubmitOrder = () => { if (cartItems.length === 0) return; const totalPrice = cartItems.reduce( (total, currentValue) => total + (currentValue.price || 0), 0 ); const orderItems = [...cartItems]; const newOrderData: OrderType = { orderItems, paymentPreference, totalPrice, date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }), id: uuid() }; completeOrder(newOrderData); }; // 在组件中添加提交按钮 // <button onClick={handleSubmitOrder}>确认提交订单</button>
内容的提问来源于stack exchange,提问作者Pedro Schinke
相关产品推荐
相关产品推荐

