购物车状态管理:React + Zustand 用着比 Redux 轻

购物车是前端最常见的状态管理场景:加商品、改数量、算总价,这些状态要跨多个组件共享(顶部角标、列表页、结算页都要读)。React 自带的 useState 管单个组件够用,跨组件共享就麻烦。Zustand 是个极轻的状态库,一个 create 就生成一个全局 store,组件里取片段用、改状态调方法,没有 Provider 嵌套、没有模板代码。这篇文章把购物车这一套跑通。
一、为什么用 Zustand 而不是 useState 往上透传
如果只用 useState,购物车状态得放在最顶层组件,再一层层 props 传给需要的子组件——中间那些不需要它的组件也得跟着接、跟着传,叫“prop drilling”。组件一多就乱。状态库的解法是把状态挪到组件树之外的一个 store 里,谁要用谁直接取,不用层层透传。
Redux 也能做,但模板代码偏多(action、reducer、dispatch)。Zustand 走极简路线:一个函数定义状态和改状态的方法,组件里用 hook 拿,心智负担小很多。
二、用一个 create 定义购物车 store
store 文件里用 create,返回一个对象:里面放状态(items)和修改状态的方法(add、remove、changeQty)。方法里用 set 更新状态,Zustand 会自动通知用到的组件重渲染。
// store.js
import { create } from 'zustand';
export const useCart = create((set) => ({
items: [],
add: (product) =>
set((s) => ({ items: [...s.items, { ...product, qty: 1 }] })),
changeQty: (id, qty) =>
set((s) => ({
items: s.items.map((it) => (it.id === id ? { ...it, qty } : it)),
})),
remove: (id) => set((s) => ({ items: s.items.filter((it) => it.id !== id) })),
}));
这里 set 接收的是“基于旧状态算新状态”的函数,返回要更新的那部分。items 是不可变更新(返回新数组),这是 React 正确触发渲染的前提,别直接 s.items.push。
三、在组件里取状态和改状态
商品卡片组件里调 add 把商品加进购物车;购物车列表里读 items 渲染、调 changeQty 和 remove:
// ProductCard.jsx
import { useCart } from './store';
export default function ProductCard({ product }) {
const add = useCart((s) => s.add);
return <button onClick={() => add(product)}>加入购物车</button>;
}
// Cart.jsx
import { useCart } from './store';
export default function Cart() {
const items = useCart((s) => s.items);
const changeQty = useCart((s) => s.changeQty);
return items.map((it) => (
<li key={it.id}>
{it.name} × {it.qty}
<button onClick={() => changeQty(it.id, it.qty + 1)}>+</button>
</li>
));
}
注意 useCart((s) => s.add) 这种“只取要用到的片段”的写法:组件只在它取的那个片段变化时才重渲染,避免无关更新拖累性能。
四、总价这种派生数据怎么算
总价不是单独存的状态,而是从 items 算出来的,叫“派生状态”。在组件里现算即可,不要存一份容易和 items 对不上的冗余值:
const total = useCart((s) => s.items.reduce((sum, it) => sum + it.price * it.qty, 0));
reduce 把每项单价乘数量累加。派生数据放在取值处计算,源数据唯一,永远不会算错。
五、几个容易踩的点
第一,更新数组/对象务必返回新引用,直接改原对象 React 感知不到。第二,只订阅需要的片段,别 const s = useCart() 全量订阅,否则任何状态变都触发本组件重渲染。第三,store 默认在内存,刷新页面购物车就空;要持久化用 Zustand 的 persist 中间件把状态存到 localStorage,几行配置即可。
六、小结
React + Zustand 管购物车,核心是“一个 create 定义 store、组件里用 hook 取片段/调方法”。比 useState 逐层透传干净,比 Redux 模板少。和之前 Vue3 用 Pinia 记账本的思路相通——都是把共享状态外置、组件按需取用,只是 API 形态不同。理解 store + 不可变更新 + 选择性订阅这三点,购物车、待办、用户登录态都能照这套写。
- 点赞
- 收藏
- 关注作者
评论(0)