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

React电商购物车localStorage持久化 刷新页面后状态丢失问题

问题根因

当前代码仅实现了购物车状态变化时写入localStorage的逻辑,存在3个核心问题导致刷新后状态丢失:

  1. useReducer初始化时硬编码使用空的defaultCartState,从未读取localStorage中已存储的购物车数据作为初始值
  2. reducer中写了localStorage.getItem("cart")但完全没有使用该返回值,且reducer作为纯函数不应该包含读取本地存储这类副作用逻辑
  3. 写入localStorage时仅存储了商品列表items,没有同步存储总金额totalAmount,同时未补全REMOVE_CART_ITEM的分支处理逻辑

修复方案

  • 实现本地存储读取逻辑,作为useReducer的初始状态:通过惰性初始化的方式,在useReducer首次执行时读取localStorage数据,处理解析异常、数据格式非法的兜底场景,避免每次渲染都重复读取本地存储
  • 补全reducer的删除商品逻辑:修正reducer分支判断,实现删除商品时的列表更新、总金额扣减逻辑
  • 修正本地存储写入逻辑:状态变化时将商品列表和总金额完整写入localStorage,避免数据缺失

修复后完整代码

import React from "react";
import CartContext from "./cart-context";
import { useReducer, useEffect } from "react";

// 兜底默认状态
const fallbackCartState = {
  items: [],
  totalAmount: 0,
};

// 从本地存储读取初始购物车状态
const getStoredCartState = () => {
  const storedData = localStorage.getItem("cart");
  if (!storedData) return fallbackCartState;
  try {
    const parsedData = JSON.parse(storedData);
    // 校验数据格式合法性,防止脏数据导致页面报错
    if (Array.isArray(parsedData.items) && typeof parsedData.totalAmount === "number") {
      return parsedData;
    }
    return fallbackCartState;
  } catch (err) {
    // 解析失败时清除脏数据,返回兜底状态
    localStorage.removeItem("cart");
    return fallbackCartState;
  }
};

const cartReducer = (state, action) => {
  if (action.type === "ADD_CART_ITEM") {
    const newItem = action.item;
    // 可按需扩展:判断购物车是否已存在同id商品,存在则累加数量而不是重复插入
    const updatedItems = state.items.concat(newItem);
    const updatedTotalAmount = state.totalAmount + newItem.price;
    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount,
    };
  }

  if (action.type === "REMOVE_CART_ITEM") {
    const targetItemIndex = state.items.findIndex(item => item.id === action.id);
    const targetItem = state.items[targetItemIndex];
    if (!targetItem) return state;
    
    const updatedTotalAmount = state.totalAmount - targetItem.price;
    let updatedItems;
    // 如果商品数量为1则直接从列表移除,否则数量减1(可根据你的商品数量字段逻辑调整)
    if (targetItem.amount === 1) {
      updatedItems = state.items.filter(item => item.id !== action.id);
    } else {
      const updatedItem = {...targetItem, amount: targetItem.amount - 1};
      updatedItems = [...state.items];
      updatedItems[targetItemIndex] = updatedItem;
    }
    return {
      items: updatedItems,
      totalAmount: updatedTotalAmount
    };
  }

  // 未匹配到action类型时返回原状态
  return state;
};

export default function CartProvider(props) {
  // useReducer第三个参数为惰性初始化函数,首次执行时会调用该函数获取初始状态
  const [cartState, dispatchCartAction] = useReducer(
    cartReducer,
    undefined,
    getStoredCartState
  );

  const addItemToCartHandler = (item) => {
    dispatchCartAction({ type: "ADD_CART_ITEM", item: item });
  };

  const removeItemFromCartHandler = (id) => {
    dispatchCartAction({ type: "REMOVE_CART_ITEM", id: id });
  };

  const cartContext = {
    items: cartState.items,
    totalAmount: cartState.totalAmount,
    addItem: addItemToCartHandler,
    removeItem: removeItemFromCartHandler,
  };

  // 购物车状态变化时同步写入本地存储
  useEffect(() => {
    localStorage.setItem("cart", JSON.stringify(cartState));
  }, [cartState]);

  return (
    <CartContext.Provider value={cartContext}>
      {props.children}
    </CartContext.Provider>
  );
}

额外说明

  • 不要在reducer内部执行localStorage读写、接口请求这类副作用操作,所有副作用都应该放在useEffect或者事件处理函数中执行
  • 读取本地存储时一定要加try/catch和格式校验,避免用户手动修改本地存储、旧版本数据格式不兼容导致页面白屏
  • 当前ADD_CART_ITEM逻辑是直接追加商品,如果你需要实现相同商品累加数量的逻辑,可以在ADD分支里先判断同id商品是否存在,再做对应更新即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 06:03:24