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

举报
海是岛思念的泪 发表于 2026/09/05 08:36:11 2026/09/05
【摘要】 用 React + Zustand 写购物车状态管理,说明为什么用状态库避免 prop drilling,以及 create 定义 store、选择性订阅、派生总价、不可变更新的写法。

image.png

购物车是前端最常见的状态管理场景:加商品、改数量、算总价,这些状态要跨多个组件共享(顶部角标、列表页、结算页都要读)。React 自带的 useState 管单个组件够用,跨组件共享就麻烦。Zustand 是个极轻的状态库,一个 create 就生成一个全局 store,组件里取片段用、改状态调方法,没有 Provider 嵌套、没有模板代码。这篇文章把购物车这一套跑通。

一、为什么用 Zustand 而不是 useState 往上透传

如果只用 useState,购物车状态得放在最顶层组件,再一层层 props 传给需要的子组件——中间那些不需要它的组件也得跟着接、跟着传,叫“prop drilling”。组件一多就乱。状态库的解法是把状态挪到组件树之外的一个 store 里,谁要用谁直接取,不用层层透传。

Redux 也能做,但模板代码偏多(action、reducer、dispatch)。Zustand 走极简路线:一个函数定义状态和改状态的方法,组件里用 hook 拿,心智负担小很多。

二、用一个 create 定义购物车 store

store 文件里用 create,返回一个对象:里面放状态(items)和修改状态的方法(addremovechangeQty)。方法里用 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 渲染、调 changeQtyremove

// 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 + 不可变更新 + 选择性订阅这三点,购物车、待办、用户登录态都能照这套写。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。