React购物车如何将countInStock存入localStorage实现状态持久化
问题背景
开发购物车应用时存在如下问题:商品库存因用户加购耗尽后,加购按钮会正常置为禁用状态,但页面刷新后按钮会恢复为可点击状态,需要将product.countInStock字段持久化保存到localStorage,保证刷新后按钮状态正确。
原有代码
import { Button } from 'react-bootstrap'; import { Card } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import React, { useContext } from 'react'; import Rating from './Rating'; import axios from 'axios'; import { Store } from '../Store'; function Product(props){ const {product} = props; const {state , dispatch:ctxDispatch} = useContext(Store); const {cart: {cartItems}} = state const addToCartHandler = async (item )=>{ const existItem = cartItems.find((x)=> x._id === product._id); const quantity = existItem ? existItem.quantity+1:1 ; const {data} = await axios.get(`/api/products/${item._id}`); if(data.countInStock < quantity){ window.alert('sorry product is out of stock') return; } product.countInStock-- ctxDispatch({ type:'CART_ADD_ITEM' , payload:{...item , quantity}, }); }; return( <Card> <Link to={`/product/${product.slug}`}> <img src={product.image} className="card-img-top" alt={product.name} /> </Link> <Card.Body> <Link to={`/product/${product.slug}`}> <Card.Title>{product.name}</Card.Title> </Link> <Rating rating={product.rating} numReviews={product.numReviews} /> <Card.Text>${product.price}</Card.Text> { product.countInStock === 0 ? ( <Button color="light" disabled={true} > Out of stock</Button> ):( <Button onClick={() => addToCartHandler(product)}>Add to cart</Button> )} </Card.Body> </Card> )}
实现方案
核心逻辑
- 封装localStorage读写方法,统一管理本地存储的库存数据,避免零散操作
- 组件初始化时,优先读取localStorage中存储的对应商品库存,没有对应记录时才使用props传入的原始库存值
- 每次加购成功扣减库存后,同步将最新库存写入localStorage
- 修正原代码直接修改props值的问题(React中props为只读,直接修改会触发不可预期的渲染问题),用组件本地状态维护当前展示的库存值
完整修改后代码
import { Button } from 'react-bootstrap'; import { Card } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import React, { useContext, useState, useEffect } from 'react'; import Rating from './Rating'; import axios from 'axios'; import { Store } from '../Store'; // 本地存储key,单独加前缀避免和其他存储字段冲突 const STOCK_STORAGE_KEY = 'shop_product_stock_map'; // 读取本地存储的所有库存映射 const getLocalStock = () => { const stockStr = localStorage.getItem(STOCK_STORAGE_KEY); return stockStr ? JSON.parse(stockStr) : {}; }; // 更新单个商品的库存到本地存储 const saveStockToLocal = (productId, countInStock) => { const stockMap = getLocalStock(); stockMap[productId] = countInStock; localStorage.setItem(STOCK_STORAGE_KEY, JSON.stringify(stockMap)); }; function Product(props){ const {product} = props; const {state , dispatch:ctxDispatch} = useContext(Store); const {cart: {cartItems}} = state; // 初始化库存:优先读本地存储,没有则用props传入的原始库存 const [currentStock, setCurrentStock] = useState(() => { const localStockMap = getLocalStock(); return localStockMap[product._id] ?? product.countInStock; }); // 每次库存更新时同步写入本地存储 useEffect(() => { saveStockToLocal(product._id, currentStock); }, [currentStock, product._id]); const addToCartHandler = async (item)=>{ const existItem = cartItems.find((x)=> x._id === product._id); const quantity = existItem ? existItem.quantity+1 : 1; const {data} = await axios.get(`/api/products/${item._id}`); // 以接口返回的最新服务端库存为准做校验,避免超卖 if(data.countInStock < quantity){ window.alert('sorry product is out of stock'); // 如果服务端库存比本地记录的小,同步更新本地库存 setCurrentStock(data.countInStock); return; } // 扣减后更新本地库存状态,会自动触发useEffect同步到localStorage setCurrentStock(prev => prev - 1); ctxDispatch({ type:'CART_ADD_ITEM', payload:{...item , quantity}, }); }; return( <Card> <Link to={`/product/${product.slug}`}> <img src={product.image} className="card-img-top" alt={product.name} /> </Link> <Card.Body> <Link to={`/product/${product.slug}`}> <Card.Title>{product.name}</Card.Title> </Link> <Rating rating={product.rating} numReviews={product.numReviews} /> <Card.Text>${product.price}</Card.Text> {currentStock === 0 ? ( <Button color="light" disabled={true}>Out of stock</Button> ):( <Button onClick={() => addToCartHandler(product)}>Add to cart</Button> )} </Card.Body> </Card> ) } export default Product;
补充说明
- 上述实现是前端侧的临时持久化方案,真实电商场景下库存校验必须以服务端返回结果为准,前端本地存储仅用于优化刷新后的展示状态,避免用户体验断层
- 如果项目中已经全局持久化了cartItems数据,也可以直接用商品原始库存 - 购物车中该商品已加购数量计算剩余库存,不需要单独存储countInStock字段,逻辑会更简洁
内容的提问来源于stack exchange,提问作者elias
相关产品推荐
相关产品推荐

