Vue进阶实战08,Vuex 实战:从 0 到 1 设计购物车的状态管理
2026/3/19 4:42:45 网站建设 项目流程

在 Vue 项目开发中,购物车是电商类应用的核心功能之一,涉及商品的添加、删除、数量修改、价格计算、选中状态管理等多维度操作。如果直接将这些状态分散在各个组件中,会导致数据流转混乱、组件通信复杂,而 Vuex(Vue 2)/Pinia(Vue 3)作为专为 Vue 设计的状态管理库,能完美解决这一问题。本文以 Vuex 4(适配 Vue 3)为例,从零拆解购物车的状态管理设计,让你彻底掌握 Vuex 在实战中的应用。

一、核心需求梳理

在设计状态管理前,先明确购物车的核心功能需求,避免盲目设计:

  1. 商品加入购物车(需判断是否已存在,存在则累加数量);
  2. 修改购物车商品数量(限制最小数量为 1);
  3. 删除购物车中的商品;
  4. 切换商品的选中状态(单个 / 全选);
  5. 实时计算选中商品的总价和总数量;
  6. 清空购物车;
  7. 持久化购物车数据(刷新页面不丢失)。

二、Vuex 核心结构设计

Vuex 的核心由State(状态)、Getter(计算属性)、Mutation(同步修改)、Action(异步 / 复杂逻辑)、Module(模块拆分)组成。购物车作为独立功能,建议拆分为独立模块,避免根模块过于臃肿。

1. 目录结构规划

src/ ├── store/ │ ├── index.js # 仓库入口,注册模块 │ └── modules/ │ └── cart.js # 购物车模块

2. 购物车模块(cart.js)核心实现

(1)State:定义购物车基础状态

State 是存储数据的唯一数据源,购物车需存储商品列表,每个商品包含核心字段:

// store/modules/cart.js const state = { // 购物车商品列表:[{ id, name, price, count, checked, image }] cartList: [] };
(2)Getter:派生状态(计算属性)

Getter 用于处理需要实时计算的状态(如总价、总数量),避免重复逻辑,且具备缓存特性:

const getters = { // 购物车商品总数 cartTotalCount: (state) => { return state.cartList.reduce((total, item) => total + item.count, 0); }, // 选中商品的总价 cartSelectedTotalPrice: (state) => { return state.cartList .filter(item => item.checked) .reduce((total, item) => total + item.price * item.count, 0) .toFixed(2); // 保留两位小数 }, // 选中商品的数量 cartSelectedCount: (state) => { return state.cartList .filter(item => item.checked) .reduce((total, item) => total + item.count, 0); }, // 是否全选 isAllChecked: (state) => { if (state.cartList.length === 0) return false; return state.cartList.every(item => item.checked); } };
(3)Mutation:同步修改状态

Vuex 规定只能通过 Mutation 修改 State,所有同步操作都需定义在 Mutation 中,便于调试和追踪:

const mutations = { // 加入购物车 ADD_TO_CART(state, goods) { // 查找商品是否已存在 const existingGoods = state.cartList.find(item => item.id === goods.id); if (existingGoods) { // 存在则累加数量 existingGoods.count += goods.count; } else { // 不存在则添加,默认选中 state.cartList.push({ ...goods, checked: true }); } // 持久化到本地存储 localStorage.setItem('cartList', JSON.stringify(state.cartList)); }, // 修改商品数量 UPDATE_GOODS_COUNT(state, { id, count }) { const goods = state.cartList.find(item => item.id === id); if (goods) { goods.count = Math.max(1, count); // 数量最小为1 localStorage.setItem('cartList', JSON.stringify(state.cartList)); } }, // 删除商品 REMOVE_GOODS(state, id) { state.cartList = state.cartList.filter(item => item.id !== id); localStorage.setItem('cartList', JSON.stringify(state.cartList)); }, // 切换单个商品选中状态 TOGGLE_GOODS_CHECKED(state, id) { const goods = state.cartList.find(item => item.id === id); if (goods) { goods.checked = !goods.checked; localStorage.setItem('cartList', JSON.stringify(state.cartList)); } }, // 全选/取消全选 TOGGLE_ALL_CHECKED(state, isChecked) { state.cartList.forEach(item => { item.checked = isChecked; }); localStorage.setItem('cartList', JSON.stringify(state.cartList)); }, // 清空购物车 CLEAR_CART(state) { state.cartList = []; localStorage.removeItem('cartList'); }, // 从本地存储加载购物车 LOAD_CART_FROM_LOCAL(state) { const cartList = localStorage.getItem('cartList'); if (cartList) { state.cartList = JSON.parse(cartList); } } };
(4)Action:处理异步 / 复杂逻辑

Action 用于处理异步操作(如请求接口获取购物车数据)或封装复杂的同步逻辑,通过commit调用 Mutation:

const actions = { // 异步加入购物车(示例:可先请求接口,再加入本地) async addToCart({ commit }, goods) { try { // 模拟接口请求:验证商品库存、价格等 // const res = await api.checkGoods(goods.id); // if (res.code !== 200) throw new Error(res.msg); // 接口请求成功后,提交Mutation修改状态 commit('ADD_TO_CART', goods); return { success: true, msg: '加入购物车成功' }; } catch (error) { return { success: false, msg: error.message || '加入购物车失败' }; } }, // 初始化加载购物车 initCart({ commit }) { commit('LOAD_CART_FROM_LOCAL'); } };
(5)导出模块
export default { namespaced: true, // 开启命名空间,避免模块间命名冲突 state, getters, mutations, actions };

3. 注册购物车模块(store/index.js)

// store/index.js import { createStore } from 'vuex'; import cart from './modules/cart'; export default createStore({ modules: { cart // 注册购物车模块,命名空间为cart } });

4. 在 Vue 项目中挂载 Store

// main.js import { createApp } from 'vue'; import App from './App.vue'; import store from './store'; const app = createApp(App); app.use(store); app.mount('#app');

三、组件中使用购物车状态

1. 初始化加载购物车

在 App.vue 的onMounted中初始化购物车数据:

<!-- App.vue --> <script setup> import { onMounted } from 'vue'; import { useStore } from 'vuex'; const store = useStore(); onMounted(() => { store.dispatch('cart/initCart'); // 调用命名空间下的action }); </script>

2. 商品页:加入购物车

<!-- GoodsDetail.vue --> <template> <div class="goods-detail"> <h3>{{ goods.name }}</h3> <p>价格:¥{{ goods.price }}</p> <div class="count"> <button @click="count--" :disabled="count <= 1">-</button> <span>{{ count }}</span> <button @click="count++">+</button> </div> <button @click="addToCart">加入购物车</button> </div> </template> <script setup> import { ref } from 'vue'; import { useStore } from 'vuex'; import { ElMessage } from 'element-plus'; // 示例:UI组件库提示 const store = useStore(); // 模拟商品数据 const goods = { id: 1, name: 'Vue实战教程', price: 99, image: 'xxx.jpg' }; const count = ref(1); // 加入购物车 const addToCart = async () => { const res = await store.dispatch('cart/addToCart', { ...goods, count: count.value }); if (res.success) { ElMessage.success(res.msg); count.value = 1; // 重置数量 } else { ElMessage.error(res.msg); } }; </script>

3. 购物车页面:展示与操作

<!-- Cart.vue --> <template> <div class="cart"> <div class="cart-header"> <label> <input type="checkbox" v-model="isAllChecked" @change="toggleAllChecked" /> 全选 </label> <button @click="clearCart">清空购物车</button> </div> <div class="cart-list" v-if="cartList.length"> <div class="cart-item" v-for="item in cartList" :key="item.id"> <label> <input type="checkbox" v-model="item.checked" @change="toggleGoodsChecked(item.id)" /> </label> <img :src="item.image" alt="" class="goods-img" /> <div class="goods-name">{{ item.name }}</div> <div class="goods-price">¥{{ item.price }}</div> <div class="goods-count"> <button @click="updateCount(item.id, item.count - 1)">-</button> <span>{{ item.count }}</span> <button @click="updateCount(item.id, item.count + 1)">+</button> </div> <div class="goods-total">¥{{ (item.price * item.count).toFixed(2) }}</div> <button @click="removeGoods(item.id)" class="remove-btn">删除</button> </div> </div> <div class="cart-empty" v-else>购物车为空~</div> <div class="cart-footer" v-if="cartList.length"> <p>选中商品数量:{{ cartSelectedCount }}</p> <p>总价:¥{{ cartSelectedTotalPrice }}</p> <button class="pay-btn">去结算</button> </div> </div> </template> <script setup> import { computed } from 'vue'; import { useStore } from 'vuex'; const store = useStore(); // 映射State(命名空间下需指定模块) const cartList = computed(() => store.state.cart.cartList); // 映射Getter const cartSelectedCount = computed(() => store.getters['cart/cartSelectedCount']); const cartSelectedTotalPrice = computed(() => store.getters['cart/cartSelectedTotalPrice']); const isAllChecked = computed(() => store.getters['cart/isAllChecked']); // 全选/取消全选 const toggleAllChecked = (e) => { store.commit('cart/TOGGLE_ALL_CHECKED', e.target.checked); }; // 切换单个商品选中状态 const toggleGoodsChecked = (id) => { store.commit('cart/TOGGLE_GOODS_CHECKED', id); }; // 修改商品数量 const updateCount = (id, count) => { store.commit('cart/UPDATE_GOODS_COUNT', { id, count }); }; // 删除商品 const removeGoods = (id) => { store.commit('cart/REMOVE_GOODS', id); }; // 清空购物车 const clearCart = () => { store.commit('cart/CLEAR_CART'); }; </script>

四、关键优化点

1. 命名空间(namespaced)

开启namespaced: true后,模块的 Getter、Mutation、Action 会自动添加模块名前缀,避免多模块下的命名冲突,这是大型项目的必备实践。

2. 数据持久化

通过 localStorage 存储购物车数据,页面刷新后调用LOAD_CART_FROM_LOCALmutation 恢复数据;生产环境中也可使用vuex-persistedstate插件简化持久化逻辑。

3. 边界处理

  • 商品数量限制最小为 1,避免负数;
  • 全选状态需判断购物车是否为空,避免空数组时every返回true
  • 价格计算后保留两位小数,符合电商场景需求。

4. 异步逻辑封装

将接口请求等异步操作放在 Action 中,组件只负责触发 Action 和处理结果,符合 “状态管理与视图解耦” 的设计思想。

五、Vue 3 迁移 Pinia 的思路

Vue 3 官方更推荐使用 Pinia 替代 Vuex,Pinia 去掉了 Mutation、简化了模块管理,购物车状态管理可无缝迁移:

// store/cart.js(Pinia版本) import { defineStore } from 'pinia'; export const useCartStore = defineStore('cart', { state: () => ({ cartList: [] }), getters: { // 同Vuex的Getter逻辑 }, actions: { // 直接在actions中修改状态,无需Mutation addToCart(goods) { // 原Mutation逻辑 }, // 其他操作方法... } });

六、总结

购物车的状态管理核心是 “统一数据源 + 清晰的操作逻辑”:

  • State 存储基础数据,Getter 处理派生数据,Mutation 保证同步修改的可追踪性,Action 封装异步 / 复杂逻辑;
  • 模块拆分、命名空间、数据持久化是实战中的关键优化手段;
  • 组件只需通过useStore(Vuex)/useCartStore(Pinia)获取状态和触发操作,实现视图与状态的解耦。

本文的购物车设计覆盖了电商场景的核心需求,你可在此基础上扩展库存校验、优惠券、地址选择等功能,进一步完善电商应用的状态管理体系。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询