React电商购物车localStorage持久化 刷新页面后状态丢失问题
问题根因
当前代码仅实现了购物车状态变化时写入localStorage的逻辑,存在3个核心问题导致刷新后状态丢失:
useReducer初始化时硬编码使用空的defaultCartState,从未读取localStorage中已存储的购物车数据作为初始值- reducer中写了
localStorage.getItem("cart")但完全没有使用该返回值,且reducer作为纯函数不应该包含读取本地存储这类副作用逻辑 - 写入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
相关产品推荐
相关产品推荐

