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
相关产品推荐
相关产品推荐

