用Redux Toolkit管理购物车GoCart cartSlice源码逐行讲解【免费下载链接】gocartGoCart is a free and open-source multi-vendor e-commerce template built with Next.js and Tailwind CSS. It features a modern design, responsive layout and essential pages to help you launch scalable online stores and marketplaces with ease.项目地址: https://gitcode.com/gh_mirrors/goca/gocartGoCart 是一个基于 Next.js 与 Tailwind CSS 构建的免费开源多商家电商模板而本文的主角就是它用Redux Toolkit实现的购物车状态管理。我们将逐行拆解核心文件lib/features/cart/cartSlice.js带你快速看懂createSlice、四个 reducer 以及购物车数量是如何在页面间实时同步的。 项目速览GoCart 电商模板在深入源码之前先了解一下这个项目的定位。GoCart 提供了一套完整的电商页面骨架商品展示、商品详情、购物车、订单、店铺管理后台等采用响应式布局适合用来搭建可扩展的在线商店和市场平台。这类模板中最经典的交互之一就是购物车——点一次导航栏上的数字就加 1。这个看似简单的功能背后正是 Redux Toolkit 发挥价值的地方用一个集中的状态源让所有页面共享同一份购物车数据。️ 先看全局Redux Store 是怎么组装的GoCart 把所有状态拆成了 4 个独立的 slice购物车、商品列表、收货地址、评分。它们最终在 lib/store.js 中通过configureStore组装到一起export const makeStore () { return configureStore({ reducer: { cart: cartReducer, product: productReducer, address: addressReducer, rating: ratingReducer, }, }) }注意这里导出的是makeStore函数而不是一个 store 实例。为什么因为 Next.js 是前后端同构的如果直接共享同一个实例会在服务端请求之间串数据。再看 app/StoreProvider.js它用useRef保证每个客户端会话只创建一个 storeconst storeRef useRef(undefined) if (!storeRef.current) { storeRef.current makeStore() } return Provider store{storeRef.current}{children}/Provider这样一来整个应用树里任何组件都能通过useSelector读取购物车状态、通过dispatch发出变更。 核心源码cartSlice 逐行讲解下面就是本文的主角完整文件位于 lib/features/cart/cartSlice.js总共只有 43 行import { createSlice } from reduxjs/toolkit const cartSlice createSlice({ name: cart, initialState: { total: 0, cartItems: {}, }, reducers: { addToCart: (state, action) { const { productId } action.payload if (state.cartItems[productId]) { state.cartItems[productId] } else { state.cartItems[productId] 1 } state.total 1 }, removeFromCart: (state, action) { const { productId } action.payload if (state.cartItems[productId]) { state.cartItems[productId]-- if (state.cartItems[productId] 0) { delete state.cartItems[productId] } } state.total - 1 }, deleteItemFromCart: (state, action) { const { productId } action.payload state.total - state.cartItems[productId] ? state.cartItems[productId] : 0 delete state.cartItems[productId] }, clearCart: (state) { state.cartItems {} state.total 0 }, } }) export const { addToCart, removeFromCart, clearCart, deleteItemFromCart } cartSlice.actions export default cartSlice.reducer别被行数吓到我们一段一段拆开看。1️⃣ 初始状态只有两个字段initialState定义了整个 slice 的数据结构字段类型含义total数字购物车商品总数量导航栏角标就用它cartItems对象以productId为键、数量为值的映射表比如购物车里有 2 件商品 A、1 件商品 B状态就是{ total: 3, cartItems: { A: 2, B: 1 } }这是一个非常省心的设计不存商品名称、价格、图片只存哪个商品买了几件商品信息统一从productslice 里查避免数据冗余。2️⃣name: cartaction 类型的前缀name字段决定了 action 类型的命名规则。addToCart这个 reducer 实际发出的 action 类型是字符串cart/addToCart。每个 slice 用自己的命名空间多个 slice 合并进同一个 store 时就不会互相撞名。3️⃣addToCart加购一个商品if (state.cartItems[productId]) { state.cartItems[productId] } else { state.cartItems[productId] 1 } state.total 1逻辑很简单这个商品已在车里就数量加 1不在车里就初始化为 1。这里有个初学者最关心的问题为什么可以直接写state.cartItems[productId]这不是修改了原状态吗答案是 RTK 的createSlice内部用了 Immer 库。你可以在 reducer 里像直接改对象一样写代码Immer 会在背后自动完成不可变immutable更新并返回全新的状态对象。这让你从繁琐的Object.assign、数组展开中解脱出来——这也是 Redux Toolkit 相比老版 Redux 最大的体验提升。4️⃣removeFromCart减购一个商品state.cartItems[productId]-- if (state.cartItems[productId] 0) { delete state.cartItems[productId] } state.total - 1和addToCart对称但多了一个关键细节数量减到 0 时把该商品从cartItems中彻底删掉。为什么不能留一个productId: 0因为购物车页遍历cartItems时0 数量的商品仍会显示一行记录。删键让状态始终保持干净——购物车里只存真正有数量的商品。5️⃣deleteItemFromCart一键清空某商品这是购物车页垃圾桶按钮️背后的 reducer。它和逐个减的区别在于一次性把数量清零所以total要减去该商品的全部数量state.total - state.cartItems[productId] ? state.cartItems[productId] : 0 delete state.cartItems[productId]三元表达式? : 0是个防御性写法万一这个商品本就不在车里也不会把total减成负数。6️⃣clearCart清空整个购物车state.cartItems {} state.total 0两个字段直接重置常见于下单成功后清空购物车的场景。7️⃣ 最后两行导出 actions 和 reducerexport const { addToCart, removeFromCart, clearCart, deleteItemFromCart } cartSlice.actions export default cartSlice.reducercartSlice.actions是四个action 创建器组件里调用addToCart({ productId })就能拿到一个标准的{ type: cart/addToCart, payload: { productId } }对象reducer则是所有 reducer 合并后的函数交给 store 使用。️ 页面实战状态如何在组件间流动理解了 slice再看 GoCart 里三个典型消费场景就一目了然了。商品详情页components/ProductDetails.jsx用户点加入购物车时 dispatch 一个addToCart商品详情页的数量选择器则用useSelector(state state.cart.cartItems)实时读出当前已加购数量。加减数量计数器components/Counter.jsxconst { cartItems } useSelector(state state.cart); const dispatch useDispatch(); const addToCartHandler () { dispatch(addToCart({ productId })) }/-两个按钮分别派发addToCart和removeFromCart中间的数字则直接来自cartItems[productId]。这就是状态在 store视图只是状态的投影。购物车页app/(public)/cart/page.jsx/cart/page.jsx)它把cartItems这个映射表和productslice 里的商品列表做合并生成带图片、名称、单价的行数据并实时计算总价导航栏components/Navbar.jsx只需要一行——const cartCount useSelector(state state.cart.total)商品详情页点了加购导航栏角标立刻 1无需任何手动同步代码。这就是集中式状态管理的直观收益。 新手要点小结学完这 43 行源码你可以带走这几条通用经验slice 即模板createSlice({ name, initialState, reducers })三件套任何状态模块地址、评分、订单都是同样的写法GoCart 的 4 个 slice 可以互相参照Immer 是超能力state.xxx这种直接改的写法在 RTK 里是安全且推荐的状态设计要克制购物车只存productId - 数量详情数据按需从别的 slice 查这是避免数据不一致的关键减到 0 要删键保持状态干净渲染逻辑会更简单Next.js 里用useRef惰性建 store防止同构环境下的状态污染这个套路值得记住 想动手练习的话可以试着给 cartSlice 增加一个updateQuantityreducer直接设定数量再把购物车页的加减按钮改用它你会对一个 reducer 如何影响所有订阅组件有更深的体会。✅ 总结GoCart 用不到 50 行的 lib/features/cart/cartSlice.js 就撑起了整套购物车功能4 个 reducer 覆盖加、减、删、清四种操作配合 lib/store.js 中的 store 组装与 app/StoreProvider.js 的上下文注入商品页、购物车页、导航栏三处 UI 天然保持同步。对于想用 Redux Toolkit 做前端状态管理的新手来说这份源码是一个麻雀虽小、五脏俱全的优质范本——读透它再复杂的购物车、收藏夹、订阅列表模块也不过是换个name的 slice 而已。【免费下载链接】gocartGoCart is a free and open-source multi-vendor e-commerce template built with Next.js and Tailwind CSS. It features a modern design, responsive layout and essential pages to help you launch scalable online stores and marketplaces with ease.项目地址: https://gitcode.com/gh_mirrors/goca/gocart创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考