##一、为什么需要 Vuex??
在 Vue2 项目开发中,如果你遇到以下问题,就该考虑使用 Vuex 了:
-
多个组件共享同一状态(如用户信息、购物车数据),组件间传参层层嵌套,代码冗余且易出错;
-
不同组件需要修改同一状态,通过事件总线(Event Bus)会导致数据流向混乱,难以调试;
-
希望状态变更可追踪,便于定位问题。
Vuex 把组件的共享状态抽取出来,以一个全局单例模式管理,让组件树构成一个巨大的“视图”,不管在哪个组件,都能获取状态或触发行为,同时保证所有状态的变更都遵循统一的规则,使代码可预测。
二、Vuex 的核心概念(Vue2 版本)
Vuex 的核心由 State、Getter、Mutation、Action、Module 五部分组成,我们先理清每个部分的作用:
1. State:唯一数据源
State 是 Vuex 存储状态(数据)的地方,相当于组件中的 data,但它是全局的,所有组件都能访问。
核心特点:
-
单一状态树:一个应用只有一个 Store 实例,所有状态集中管理;
-
响应式:State 中的数据是响应式的,组件获取后,数据变更会自动更新视图。
2. Getter:计算属性
Getter 相当于 Vue 组件中的 computed,用于对 State 中的数据进行二次处理(如过滤、计算),结果会被缓存,只有依赖的数据变化时才会重新计算。
3. Mutation:唯一修改状态的方式
Mutation 是修改 State 的唯一入口,必须是同步函数(异步操作会导致状态变更无法追踪)。每个 Mutation 有一个字符串类型的 type 和一个 handler 函数,handler 函数接收 state 作为第一个参数,可接收第二个参数(载荷 payload)。
4. Action:处理异步操作
Action 用于处理异步逻辑(如接口请求),不能直接修改 State,必须通过提交 Mutation 来修改状态。Action 可以包含任意异步操作,支持提交多个 Mutation。
5. Module:模块化管理
当项目规模较大时,单一 State 会变得臃肿,Module 允许将 Store 分割成多个模块,每个模块拥有自己的 State、Getter、Mutation、Action,便于维护。
三、Vue2 中 Vuex 的使用步骤
1. 环境准备
首先确保项目是 Vue2 环境,安装对应版本的 Vuex(Vue2 对应 Vuex 3.x,Vue3 对应 Vuex 4.x):
# 安装 Vuex 3.x
npm install vuex@3 --save
# 或
yarn add vuex@3
2. 创建 Store 实例
在项目 src 目录下新建 store 文件夹,创建 index.js:
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 注册 Vuex
Vue.use(Vuex)
// 定义 Store
const store = new Vuex.Store({
// 1. 状态数据
state: {
// 用户信息
userInfo: {
name: '',
token: ''
},
// 购物车列表
cartList: []
},
// 2. 计算属性
getters: {
// 计算购物车商品总数
cartTotalCount: state => {
return state.cartList.reduce((total, item) => total + item.quantity, 0)
},
// 过滤已选中的购物车商品
selectedCartItems: state => {
return state.cartList.filter(item => item.checked)
}
},
// 3. 同步修改状态
mutations: {
// 设置用户信息
SET_USER_INFO(state, payload) {
state.userInfo = { ...state.userInfo, ...payload }
},
// 添加商品到购物车
ADD_TO_CART(state, goods) {
// 先判断商品是否已存在
const existItem = state.cartList.find(item => item.id === goods.id)
if (existItem) {
existItem.quantity += 1
} else {
state.cartList.push({ ...goods, quantity: 1, checked: true })
}
},
// 清空购物车
CLEAR_CART(state) {
state.cartList = []
}
},
// 4. 异步操作
actions: {
// 异步获取用户信息(模拟接口请求)
async fetchUserInfo({ commit }, userId) {
try {
// 模拟接口请求
const res = await new Promise(resolve => {
setTimeout(() => {
resolve({
name: '张三',
token: 'abc123456'
})
}, 1000)
})
// 提交 Mutation 修改状态
commit('SET_USER_INFO', res)
return res
} catch (error) {
console.error('获取用户信息失败:', error)
throw error
}
},
// 异步清空购物车(模拟接口+修改状态)
async clearCart({ commit }) {
try {
// 模拟调用清空购物车接口
await new Promise(resolve => setTimeout(resolve, 500))
// 提交 Mutation
commit('CLEAR_CART')
} catch (error) {
console.error('清空购物车失败:', error)
}
}
}
})
export default store
3. 在 Vue 项目中挂载 Store
修改 src/main.js,将 Store 挂载到 Vue 实例:
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 引入 Store
new Vue({
el: '#app',
store, // 挂载到Vue实例
render: h => h(App)
})
4. 组件中使用 Vuex
(1)获取 State 数据
有两种方式:直接通过 this.$store.state 访问,或使用 mapState 辅助函数(推荐,简化代码)。
<template>
<div class="user-info">
<p>用户名:{{ userInfo.name }}</p>
<p>购物车商品数:{{ cartList.length }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 方式1:直接访问
// userInfo() {
// return this.$store.state.userInfo
// },
// cartList() {
// return this.$store.state.cartList
// }
// 方式2:mapState 辅助函数(推荐)
...mapState(['userInfo', 'cartList'])
}
}
</script>
(2)使用 Getter
同理,可通过 this.$store.getters 或 mapGetters 辅助函数:
<template>
<div class="cart">
<p>购物车总数:{{ cartTotalCount }}</p>
<p>已选中商品:{{ selectedCartItems.length }} 件</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['cartTotalCount', 'selectedCartItems'])
}
}
</script>
(3)提交 Mutation 修改状态
Mutation 必须通过 commit 触发,支持 this.$store.commit 或 mapMutations 辅助函数:
<template>
<button @click="addGoods">添加商品到购物车</button>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
methods: {
addGoods() {
// 方式1:直接 commit
// this.$store.commit('ADD_TO_CART', { id: 1, name: 'Vue实战教程', price: 99 })
// 方式2:mapMutations 辅助函数
this.ADD_TO_CART({ id: 1, name: 'Vue实战教程', price: 99 })
},
...mapMutations(['ADD_TO_CART'])
}
}
</script>
(4)触发 Action 处理异步
Action 通过 dispatch 触发,支持 this.$store.dispatch 或 mapActions 辅助函数:
<template>
<div>
<button @click="getUserInfo">获取用户信息</button>
<button @click="clearCart">清空购物车</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
async getUserInfo() {
// 方式1:直接 dispatch
// await this.$store.dispatch('fetchUserInfo', 1001)
// 方式2:mapActions 辅助函数
await this.fetchUserInfo(1001)
console.log('用户信息已获取:', this.userInfo)
},
async clearCart() {
await this.clearCart()
console.log('购物车已清空')
},
...mapActions(['fetchUserInfo', 'clearCart'])
},
computed: {
userInfo() {
return this.$store.state.userInfo
}
}
}
</script>
5. 模块化(Module)实战
当项目复杂时,拆分 Store 为多个模块,例如拆分 user 和 cart 模块:
步 骤1:创建模块文件
// src/store/modules/user.js
export default {
namespaced: true, // 开启命名空间,避免模块间命名冲突
state: {
userInfo: { name: '', token: '' }
},
getters: {
isLogin: state => !!state.userInfo.token
},
mutations: {
SET_USER_INFO(state, payload) {
state.userInfo = { ...state.userInfo, ...payload }
}
},
actions: {
async fetchUserInfo({ commit }, userId) {
const res = await new Promise(resolve => {
setTimeout(() => resolve({ name: '张三', token: 'abc123' }), 1000)
})
commit('SET_USER_INFO', res)
}
}
}
// src/store/modules/cart.js
export default {
namespaced: true,
state: {
cartList: []
},
getters: {
cartTotalCount: state => state.cartList.reduce((t, i) => t + i.quantity, 0)
},
mutations: {
ADD_TO_CART(state, goods) {
const exist = state.cartList.find(i => i.id === goods.id)
exist ? exist.quantity++ : state.cartList.push({ ...goods, quantity: 1 })
}
},
actions: {
async clearCart({ commit }) {
await new Promise(resolve => setTimeout(resolve, 500))
commit('CLEAR_CART')
}
}
}
步 骤2:整合模块到 Store
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
cart
}
})
步 骤3:组件中使用命名空间模块
<template>
<div>
<p>用户名:{{ userInfo.name }}</p>
<p>购物车总数:{{ cartTotalCount }}</p>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// 方式1:带命名空间的 mapState
...mapState('user', ['userInfo']),
// 方式2:直接访问
// userInfo() {
// return this.$store.state.user.userInfo
// },
...mapGetters('cart', ['cartTotalCount'])
},
methods: {
...mapActions('user', ['fetchUserInfo']),
...mapActions('cart', ['clearCart']),
async init() {
await this.fetchUserInfo(1001)
}
},
created() {
this.init()
}
}
</script>
四、Vuex 使用
1. 严格模式
开启严格模式后,任何不是通过 Mutation 修改 State 的操作都会抛出错误,便于调试(生产环境建议关闭):
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production', // 仅开发环境开启
// ...其他配置
})
2. 避免直接修改 State
永远不要在组件中直接修改 this.$store.state.xxx,必须通过 Mutation 或 Action 提交 Mutation,否则状态变更无法追踪。
3. 合理拆分模块
-
按业务域拆分模块(如 user、cart、order),每个模块独立维护;
-
开启
namespaced: true,避免命名冲突; -
复杂模块可进一步拆分(如 cart 下拆分子模块 cartItem、cartPrice)。
4. 异步逻辑统一放在 Action
所有异步操作(接口请求、定时器、Promise)都放在 Action 中,Mutation 只做同步的状态修改,保证状态变更可追踪。
5. 数据持久化
Vuex 状态刷新后会丢失,可结合 vuex-persistedstate 实现本地存储持久化:
npm install vuex-persistedstate@3 --save # 适配 Vuex 3.x
// src/store/index.js
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
// ...其他配置
plugins: [
createPersistedState({
key: 'vue2-vuex-demo', // 本地存储的key
paths: ['user.userInfo', 'cart.cartList'] // 需要持久化的状态路径
})
]
})
6. 避免过度使用 Vuex
并非所有数据都要放入 Vuex,只有跨组件共享、全局使用的状态(如用户信息、购物车、全局配置)才适合,组件内部的私有状态(如表单临时值)直接用组件 data 即可。
五、常见问题与解决方案
1. 模块中获取根状态/根 Getter
在模块的 Getter/Action 中,可通过第二个参数获取根状态:
// 模块中的 Getter
getters: {
// rootState 访问根状态,rootGetters 访问根 Getter
cartTotalPrice: (state, getters, rootState, rootGetters) => {
return state.cartList.reduce((t, i) => t + i.price * i.quantity, 0)
}
}
// 模块中的 Action
actions: {
async updateCart({ commit, rootState }) {
// 访问根状态的 user 信息
const token = rootState.user.userInfo.token
await api.updateCart(token, state.cartList)
}
}
2. 辅助函数简化命名空间写法
可通过绑定命名空间的方式简化代码:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('cart')
export default {
computed: {
...mapState(['cartList'])
},
methods: {
...mapActions(['addToCart'])
}
}
六、总结
-
Vuex 是 Vue2 中解决跨组件共享状态的核心方案,核心是“单向数据流”:View → Dispatch Action → Commit Mutation → Modify State → Update View;
-
Vuex 3.x 适配 Vue2,核心概念包括 State(状态)、Getter(计算)、Mutation(同步修改)、Action(异步)、Module(模块化);
-
“异步逻辑放 Action,同步修改放 Mutation”,复杂项目拆分模块并开启命名空间,结合
vuex-persistedstate实现状态持久化; -
避免过度使用 Vuex,仅管理全局共享状态,组件私有状态仍用
data维护。
If you enjoyed this, leave a comment~