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

React购物车将数量保存到localStorage时出现重复累加问题求助

购物车数量翻倍问题解决方法

问题描述

开发购物车功能时,使用useReducer和Context管理商品状态,功能正常但页面刷新后数据丢失。尝试将商品总数量存入localStorage,添加新商品时先读取localStorage中的数量再累加,但逻辑执行两次,导致最终数量是预期值的两倍。

问题原因

  1. 直接修改reducer中的state:在ADD动作里直接赋值state.totalQuantity = JSON.parse(localStorage.getItem("quantity")),违反了reducer纯函数的设计原则(reducer必须基于原有state生成新状态,不能直接修改原state),导致状态混乱。
  2. 初始状态未同步本地存储:CartProvider初始化时使用默认的defaultCartState,没有从localStorage读取已保存的购物车数据,导致state中的totalQuantity与localStorage的值不一致,叠加后出现错误。
  3. 开发环境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;

关键修改说明

  1. 初始状态同步:通过loadCartFromLocalStorage函数在初始化时读取本地存储的购物车数据,保证state与localStorage从一开始就一致。
  2. 纯函数reducer:所有状态更新都基于原state生成新对象,不再直接修改state,避免状态污染。
  3. 完整数据同步:每次状态变化时,将整个购物车对象存入localStorage,确保商品列表、总金额、总数量三者同步。
  4. 完善动作逻辑:补充了REMOVE和CLEAR动作的处理,保证所有购物车操作都能同步到本地存储。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:45:25