React购物车将数量保存到localStorage时出现重复累加问题求助
购物车数量翻倍问题解决方法
问题描述
开发购物车功能时,使用useReducer和Context管理商品状态,功能正常但页面刷新后数据丢失。尝试将商品总数量存入localStorage,添加新商品时先读取localStorage中的数量再累加,但逻辑执行两次,导致最终数量是预期值的两倍。
问题原因
- 直接修改reducer中的state:在ADD动作里直接赋值
state.totalQuantity = JSON.parse(localStorage.getItem("quantity")),违反了reducer纯函数的设计原则(reducer必须基于原有state生成新状态,不能直接修改原state),导致状态混乱。 - 初始状态未同步本地存储:CartProvider初始化时使用默认的
defaultCartState,没有从localStorage读取已保存的购物车数据,导致state中的totalQuantity与localStorage的值不一致,叠加后出现错误。 - 开发环境StrictMode放大问题:React StrictMode在开发环境下会重复调用reducer和dispatch,进一步加剧了状态不同步的问题,导致逻辑执行两次。
解决方案
- 保持reducer的纯函数特性,不直接修改原state,基于当前state计算新状态。
- 初始化useReducer时,从localStorage加载完整的购物车状态,保证初始状态与本地存储同步。
- 将完整的购物车数据(商品列表、总金额、总数量)同步到localStorage,而非仅存储数量,避免数据不一致。
修改后的代码
购物车Reducer代码
import { useReducer } from "react"; import CartContext from "./cart-context"; // 从localStorage加载初始购物车状态 const loadCartFromLocalStorage = () => { const savedCart = localStorage.getItem("cart"); return savedCart ? JSON.parse(savedCart) : { items: [], totalAmount: 0, totalQuantity: 0, }; }; const cartReducer = (state, action) => { if (action.type === "ADD") { // 基于当前state计算新的总金额和总数量 const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount; const updatedTotalQuantity = state.totalQuantity + action.item.amount; // 更新商品列表 const existingCartItemIndex = state.items.findIndex( (item) => item.id === action.item.id ); const existingCartItem = state.items[existingCartItemIndex]; let updatedItems; if (existingCartItem) { const updatedItem = { ...existingCartItem, amount: existingCartItem.amount + action.item.amount, }; updatedItems = [...state.items]; updatedItems[existingCartItemIndex] = updatedItem; } else { updatedItems = state.items.concat(action.item); } // 保存完整购物车状态到localStorage const newCartState = { items: updatedItems, totalAmount: updatedTotalAmount, totalQuantity: updatedTotalQuantity, }; localStorage.setItem("cart", JSON.stringify(newCartState)); return newCartState; } if (action.type === "REMOVE") { // 处理商品移除逻辑 const existingCartItemIndex = state.items.findIndex( (item) => item.id === action.id ); const existingCartItem = state.items[existingCartItemIndex]; let updatedItems; let updatedTotalAmount = state.totalAmount; let updatedTotalQuantity = state.totalQuantity; if (existingCartItem.amount === 1) { updatedItems = state.items.filter(item => item.id !== action.id); } else { const updatedItem = { ...existingCartItem, amount: existingCartItem.amount - 1, }; updatedItems = [...state.items]; updatedItems[existingCartItemIndex] = updatedItem; } updatedTotalAmount -= existingCartItem.price; updatedTotalQuantity -= 1; const newCartState = { items: updatedItems, totalAmount: updatedTotalAmount, totalQuantity: updatedTotalQuantity, }; localStorage.setItem("cart", JSON.stringify(newCartState)); return newCartState; } if (action.type === "CLEAR") { // 清空购物车 localStorage.removeItem("cart"); return { items: [], totalAmount: 0, totalQuantity: 0, }; } return state; };
CartProvider代码
const CartProvider = (props) => { // 使用从localStorage加载的状态作为初始值 const [cartState, dispatchCartAction] = useReducer( cartReducer, loadCartFromLocalStorage() ); const addItemToCartHandler = (item) => { dispatchCartAction({ type: "ADD", item: item }); }; const removeItemFromCartHandler = (id) => { dispatchCartAction({ type: "REMOVE", id: id }); }; const clearCartHandler = () => { dispatchCartAction({ type: "CLEAR" }); }; const cartContext = { items: cartState.items, totalAmount: cartState.totalAmount, totalQuantity: cartState.totalQuantity, addItem: addItemToCartHandler, removeItem: removeItemFromCartHandler, clearCart: clearCartHandler, }; return ( <CartContext.Provider value={cartContext}> {props.children} </CartContext.Provider> ); }; export default CartProvider;
关键修改说明
- 初始状态同步:通过
loadCartFromLocalStorage函数在初始化时读取本地存储的购物车数据,保证state与localStorage从一开始就一致。 - 纯函数reducer:所有状态更新都基于原state生成新对象,不再直接修改state,避免状态污染。
- 完整数据同步:每次状态变化时,将整个购物车对象存入localStorage,确保商品列表、总金额、总数量三者同步。
- 完善动作逻辑:补充了REMOVE和CLEAR动作的处理,保证所有购物车操作都能同步到本地存储。
内容的提问来源于stack exchange,提问作者Vos
相关产品推荐
相关产品推荐

