什么是 Vuex?
Vuex 是 Vue.js 的官方状态管理库,专为 Vue.js 应用程序设计。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
为什么需要 Vuex?
在复杂的 Vue 应用中,组件间的状态共享和通信会变得困难:
- 多层嵌套组件间的状态传递繁琐
- 兄弟组件间的通信复杂
- 多个视图依赖同一状态
- 来自不同视图的行为需要变更同一状态
Vuex 解决了这些问题,提供了:
- 单一数据源 - 所有组件共享同一状态源
- 可预测的状态变更 - 通过严格模式确保状态变更符合规则
- 模块化组织 - 大型应用状态管理的解决方案
核心概念
1. State - 状态存储
State 是应用状态的单一数据源,存储在 Vuex store 中。
javascript
import { createStore } from 'vuex';
const store = createStore({
state: {
count: 0,
user: null,
todos: []
}
});
2. Getters - 状态派生
Getters 用于从 state 中派生出新状态,类似于计算属性。
javascript
const store = createStore({
state: {
todos: [
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Master Vuex', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done);
},
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id);
}
}
});
3. Mutations - 状态变更
Mutations 是更改 Vuex store 中状态的唯一方法,必须是同步函数。
javascript
const store = createStore({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++;
},
incrementBy(state, payload) {
state.count += payload.amount;
}
}
});
4. Actions - 异步操作
Actions 用于提交 mutations,可以包含任意异步操作。
javascript
const store = createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
fetchUserData({ commit }, userId) {
return axios.get(`/api/users/${userId}`)
.then(response => {
commit('setUser', response.data);
});
}
}
});
5. Modules - 模块化
大型应用可以将 store 分割成模块,每个模块拥有自己的 state、mutations、actions、getters。
javascript
const userModule = {
namespaced: true,
state: () => ({
profile: null
}),
mutations: {
setProfile(state, profile) {
state.profile = profile;
}
},
actions: {
fetchProfile({ commit }, userId) {
// 获取用户资料
}
}
};
const store = createStore({
modules: {
user: userModule,
products: productsModule
}
});
Vuex 4 在 Vue 3 中的使用
安装与配置
bash
npm install vuex@next --save
javascript
// main.js
import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';
// 创建 Vuex store
const store = createStore({
state() {
return {
count: 0
};
},
mutations: {
increment(state) {
state.count++;
}
}
});
const app = createApp(App);
app.use(store);
app.mount('#app');
在组合式 API 中使用 Vuex
vue
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const count = computed(() => store.state.count);
const increment = () => {
store.commit('increment');
};
const incrementAsync = () => {
store.dispatch('incrementAsync');
};
return {
count,
increment,
incrementAsync
};
}
};
</script>
在选项式 API 中使用 Vuex
vue
<script>
export default {
computed: {
count() {
return this.$store.state.count;
},
// 使用 mapState 辅助函数
...mapState(['count']),
...mapState({
countAlias: 'count'
})
},
methods: {
increment() {
this.$store.commit('increment');
},
// 使用 mapMutations 辅助函数
...mapMutations(['increment']),
...mapMutations({
add: 'increment'
}),
// Actions
...mapActions(['incrementAsync']),
...mapActions({
addAsync: 'incrementAsync'
})
}
};
</script>
高级用法与最佳实践
1. 严格模式
开启严格模式后,任何非 mutation 函数的状态变更都会抛出错误。
javascript
const store = createStore({
strict: process.env.NODE_ENV !== 'production',
state: { ... },
mutations: { ... }
});
2. 表单处理
使用双向绑定处理表单时,需要使用计算属性的 setter:
vue
<template>
<input v-model="message">
</template>
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const message = computed({
get: () => store.state.message,
set: value => store.commit('updateMessage', value)
});
return { message };
}
};
</script>
3. 模块动态注册
在 store 创建之后,可以使用 store.registerModule
方法动态注册模块:
javascript
store.registerModule('myModule', {
state: () => ({ ... }),
mutations: { ... }
});
4. 插件开发
Vuex 插件是一个函数,它接收 store 作为唯一参数:
javascript
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
console.log(mutation.type);
console.log(mutation.payload);
});
};
const store = createStore({
plugins: [myPlugin],
// ...
});
5. 状态持久化
使用插件实现 Vuex 状态持久化:
javascript
import createPersistedState from 'vuex-persistedstate';
const store = createStore({
plugins: [createPersistedState()],
state: {
user: null,
preferences: {}
}
});
实战案例:电商应用状态管理
1. 项目结构
bash
src/
├── store/
│ ├── index.js # 组装模块并导出 store
│ ├── modules/
│ │ ├── cart.js # 购物车模块
│ │ ├── products.js # 商品模块
│ │ ├── user.js # 用户模块
│ │ └── orders.js # 订单模块
│ └── plugins/
│ └── persistence.js # 持久化插件
2. 购物车模块实现
javascript
// store/modules/cart.js
export default {
namespaced: true,
state: () => ({
items: [],
total: 0
}),
mutations: {
ADD_TO_CART(state, product) {
const existingItem = state.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
state.items.push({ ...product, quantity: 1 });
}
state.total += product.price;
},
REMOVE_FROM_CART(state, productId) {
const index = state.items.findIndex(item => item.id === productId);
if (index !== -1) {
const [removedItem] = state.items.splice(index, 1);
state.total -= removedItem.price * removedItem.quantity;
}
},
CLEAR_CART(state) {
state.items = [];
state.total = 0;
}
},
actions: {
async checkout({ commit, state }) {
try {
const order = {
items: state.items,
total: state.total,
timestamp: new Date().toISOString()
};
// 发送订单到服务器
await axios.post('/api/orders', order);
// 清空购物车
commit('CLEAR_CART');
return order;
} catch (error) {
throw new Error('Checkout failed');
}
}
},
getters: {
cartItemCount: state => state.items.length,
isProductInCart: state => productId => {
return state.items.some(item => item.id === productId);
}
}
};
3. 在组件中使用
vue
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
// 访问购物车状态
const cartItems = computed(() => store.state.cart.items);
const cartTotal = computed(() => store.state.cart.total);
// 访问购物车 getters
const itemCount = computed(() => store.getters['cart/cartItemCount']);
// 操作购物车
const addToCart = product => {
store.commit('cart/ADD_TO_CART', product);
};
const removeFromCart = productId => {
store.commit('cart/REMOVE_FROM_CART', productId);
};
const checkout = async () => {
try {
await store.dispatch('cart/checkout');
alert('Order placed successfully!');
} catch (error) {
alert(error.message);
}
};
return {
cartItems,
cartTotal,
itemCount,
addToCart,
removeFromCart,
checkout
};
}
};
</script>
4. 模块间通信
javascript
// store/modules/products.js
export default {
namespaced: true,
state: () => ({
products: []
}),
actions: {
async loadProducts({ commit }) {
const products = await axios.get('/api/products');
commit('SET_PRODUCTS', products);
},
// 模块间通信示例
async addToCartFromProduct({ dispatch }, product) {
// 调用购物车模块的 action
await dispatch('cart/addToCart', product, { root: true });
// 显示通知
dispatch('notifications/show', {
message: `${product.name} added to cart!`,
type: 'success'
}, { root: true });
}
}
};
Vuex 与 Pinia 的比较
特性 | Vuex | Pinia |
---|---|---|
Vue 3 支持 | Vuex 4 支持 | 专为 Vue 3 设计 |
类型支持 | 有限支持 | 一流的类型支持 |
开发体验 | 较复杂 | 更简洁直观 |
模块系统 | 需要命名空间 | 自动命名空间 |
组合式 API | 支持 | 原生支持 |
插件系统 | 完善 | 兼容 Vuex 插件 |
大小 | ~4KB | ~1KB |
学习曲线 | 较陡峭 | 较平缓 |
官方维护 | 是 | 是(Vue 核心团队) |
最佳实践总结
- 模块化组织:按功能划分模块,保持单一职责原则
- 严格命名规范:使用一致的命名规则(如 actions 使用驼峰式,mutations 使用大写)
- 异步操作处理:所有异步操作放在 actions 中,mutations 只处理同步状态变更
- 避免直接修改 state:始终通过 mutations 修改状态
- 合理使用 getters:对于派生状态使用 getters,避免重复计算
- 模块间通信:优先使用 actions 进行模块间通信
- 类型安全:在 TypeScript 项目中利用 Vuex 的类型支持
- 性能优化:避免在 getters 中进行复杂计算,必要时使用缓存
- 状态持久化:对需要持久化的状态使用插件
- 测试策略:单元测试 mutations 和 getters,集成测试 actions
迁移到 Pinia
随着 Pinia 成为 Vue 官方推荐的状态管理库,新项目建议使用 Pinia:
javascript
// 使用 Pinia 的示例
import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
total: 0
}),
actions: {
addToCart(product) {
// 直接修改 state
const existingItem = this.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
this.total += product.price;
}
},
getters: {
itemCount: (state) => state.items.length
}
});
结论
Vuex 4 为 Vue 3 提供了强大的状态管理解决方案,特别适合大型复杂应用。它通过集中式的状态管理和严格的变更规则,使应用状态变得可预测和易于调试。
对于新项目,建议考虑使用 Pinia,它提供了更简洁的 API 和更好的开发体验,同时保留了 Vuex 的核心概念。对于现有使用 Vuex 的项目,Vuex 4 仍然是一个稳定可靠的选择。
无论选择哪种状态管理方案,核心原则是一致的:
- 集中管理共享状态
- 规范状态变更流程
- 模块化组织代码
- 保持状态的可预测性和可维护性