下面,我们来系统的梳理关于 Vuex 状态管理 的基本知识点:
一、Vuex 核心概念
1.1 什么是 Vuex?
Vuex 是 Vue.js 的官方状态管理库,用于解决复杂应用中组件间状态共享和管理的难题。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化。
1.2 为什么需要状态管理?
问题 | 解决方案 | Vuex 实现 |
---|---|---|
组件通信困难 | 全局状态树 | store.state |
状态分散混乱 | 单一数据源 | 单一 store 实例 |
异步操作复杂 | 统一处理 | actions |
状态变化追踪 | 严格流程 | mutations 和 Devtools |
组件复用问题 | 解耦状态 | 状态与组件分离 |
1.3 安装与基础配置
npm install vuex@next --save
# 或
yarn add vuex@next
创建 Store:
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
在 Vue 应用中使用:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
二、核心概念详解
2.1 State - 状态容器
state: {
user: {
id: 1,
name: 'John Doe',
isAdmin: true
},
products: [],
cart: {
items: [],
total: 0
}
}
在组件中访问:
// 选项式 API
computed: {
count() {
return this.$store.state.count
}
}
// 组合式 API
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const count = computed(() => store.state.count)
return { count }
}
}
2.2 Getters - 状态派生
getters: {
// 基本 getter
cartTotal: state => state.cart.total,
// 带参数的 getter
productById: state => id => {
return state.products.find(product => product.id === id)
},
// 使用其他 getter
discountedTotal: (state, getters) => {
return getters.cartTotal * 0.9
}
}
在组件中使用:
<template>
<p>购物车总价: {{ cartTotal }}</p>
<p>折后价: {{ discountedTotal }}</p>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
return {
cartTotal: computed(() => store.getters.cartTotal),
discountedTotal: computed(() => store.getters.discountedTotal),
getProduct: id => store.getters.productById(id)
}
}
}
</script>
2.3 Mutations - 同步状态变更
mutations: {
// 基本 mutation
ADD_TO_CART(state, product) {
const existingItem = state.cart.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
state.cart.items.push({ ...product, quantity: 1 })
}
state.cart.total += product.price
},
// 使用载荷对象
UPDATE_USER(state, payload) {
state.user = { ...state.user, ...payload }
}
}
在组件中提交:
// 选项式 API
methods: {
addToCart(product) {
this.$store.commit('ADD_TO_CART', product)
}
}
// 组合式 API
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const addToCart = (product) => {
store.commit('ADD_TO_CART', product)
}
return { addToCart }
}
}
2.4 Actions - 异步操作
actions: {
// 基本 action
async fetchProducts({ commit }) {
try {
const response = await fetch('/api/products')
const products = await response.json()
commit('SET_PRODUCTS', products)
return products
} catch (error) {
commit('SET_ERROR', error.message)
throw error
}
},
// 组合多个 actions
async loadAppData({ dispatch }) {
await dispatch('fetchUser')
await dispatch('fetchProducts')
await dispatch('fetchCart')
}
}
在组件中分发:
// 选项式 API
methods: {
async loadProducts() {
try {
await this.$store.dispatch('fetchProducts')
} catch (error) {
this.showError(error.message)
}
}
}
// 组合式 API
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const fetchProducts = async () => {
try {
await store.dispatch('fetchProducts')
} catch (error) {
// 错误处理
}
}
return { fetchProducts }
}
}
2.5 Modules - 模块化
// store/modules/cart.js
const cartModule = {
namespaced: true,
state: () => ({
items: [],
total: 0
}),
mutations: {
ADD_ITEM(state, product) { /* ... */ }
},
actions: {
addToCart({ commit }, product) {
commit('ADD_ITEM', product)
}
}
}
// store/index.js
import { createStore } from 'vuex'
import cartModule from './modules/cart'
import userModule from './modules/user'
export default createStore({
modules: {
cart: cartModule,
user: userModule
}
})
在组件中使用:
// 访问模块状态
computed: {
cartItems() {
return this.$store.state.cart.items
}
}
// 提交模块 mutation
methods: {
addToCart(product) {
this.$store.commit('cart/ADD_ITEM', product)
}
}
// 分发模块 action
this.$store.dispatch('cart/addToCart', product)
三、高级应用模式
3.1 插件开发
// 持久化插件
const localStoragePlugin = store => {
// 初始化时从本地存储恢复状态
const savedState = localStorage.getItem('vuex-state')
if (savedState) {
store.replaceState(JSON.parse(savedState))
}
// 订阅 store 变化
store.subscribe((mutation, state) => {
localStorage.setItem('vuex-state', JSON.stringify(state))
})
}
// 在 store 中使用
export default createStore({
plugins: [localStoragePlugin],
// ...其他配置
})
3.2 严格模式
export default createStore({
strict: process.env.NODE_ENV !== 'production',
// ...其他配置
})
注意事项:
- 生产环境禁用严格模式以避免性能损耗
- 在严格模式下,所有状态变更必须通过 mutation
- 异步操作应使用 actions 而非直接修改状态
3.3 表单处理
<template>
<input v-model="message">
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
return {
message: computed({
get: () => store.state.message,
set: value => store.commit('UPDATE_MESSAGE', value)
})
}
}
}
</script>
3.4 组合式 API 最佳实践
// store/composables/useCart.js
import { computed } from 'vue'
import { useStore } from 'vuex'
export default function useCart() {
const store = useStore()
const cartItems = computed(() => store.state.cart.items)
const cartTotal = computed(() => store.getters['cart/total'])
const addToCart = (product) => {
store.dispatch('cart/addToCart', product)
}
const removeFromCart = (id) => {
store.commit('cart/REMOVE_ITEM', id)
}
return {
cartItems,
cartTotal,
addToCart,
removeFromCart
}
}
在组件中使用:
<script>
import useCart from '@/composables/useCart'
export default {
setup() {
const { cartItems, addToCart } = useCart()
return {
cartItems,
addToCart
}
}
}
</script>
四、项目结构设计
4.1 推荐项目结构
src/
├── store/
│ ├── index.js # 组装模块并导出 store
│ ├── modules/ # 模块目录
│ │ ├── cart.js # 购物车模块
│ │ ├── products.js # 产品模块
│ │ └── user.js # 用户模块
│ ├── plugins/ # Vuex 插件
│ │ └── persistence.js
│ └── composables/ # 组合式函数
│ ├── useCart.js
│ └── useProducts.js
4.2 模块设计示例
// store/modules/products.js
export default {
namespaced: true,
state: () => ({
items: [],
loading: false,
error: null
}),
mutations: {
SET_PRODUCTS(state, products) {
state.items = products
},
SET_LOADING(state, isLoading) {
state.loading = isLoading
},
SET_ERROR(state, error) {
state.error = error
}
},
actions: {
async fetchProducts({ commit }) {
commit('SET_LOADING', true)
try {
const response = await fetch('/api/products')
const products = await response.json()
commit('SET_PRODUCTS', products)
return products
} catch (error) {
commit('SET_ERROR', error.message)
throw error
} finally {
commit('SET_LOADING', false)
}
}
},
getters: {
featuredProducts: state => {
return state.items.filter(product => product.isFeatured)
},
productById: state => id => {
return state.items.find(product => product.id === id)
}
}
}
五、测试策略
5.1 单元测试示例
import { createStore } from 'vuex'
import cartModule from '@/store/modules/cart'
describe('cart 模块', () => {
let store
beforeEach(() => {
store = createStore({
modules: {
cart: cartModule
}
})
})
test('ADD_ITEM mutation 添加商品', () => {
const product = { id: 1, name: '商品1', price: 100 }
store.commit('cart/ADD_ITEM', product)
expect(store.state.cart.items).toHaveLength(1)
expect(store.state.cart.items[0]).toEqual({
...product,
quantity: 1
})
expect(store.state.cart.total).toBe(100)
})
test('addToCart action 提交 mutation', async () => {
const commit = jest.fn()
const product = { id: 2, name: '商品2', price: 200 }
await cartModule.actions.addToCart({ commit }, product)
expect(commit).toHaveBeenCalledWith('ADD_ITEM', product)
})
test('total getter 计算总价', () => {
store.state.cart.items = [
{ id: 1, price: 100, quantity: 2 },
{ id: 2, price: 50, quantity: 3 }
]
const total = cartModule.getters.total(store.state.cart)
expect(total).toBe(100 * 2 + 50 * 3) // 350
})
})
5.2 测试工具推荐
- Jest:JavaScript 测试框架
- Vue Test Utils:Vue 组件测试库
- MSW (Mock Service Worker):API 模拟工具
- Testing Library:用户行为测试库
六、最佳实践
6.1 状态管理原则
- 单一数据源:整个应用共享一个 store 实例
- 响应式状态:所有需要在组件中使用的状态必须是响应式的
- 不直接修改状态:通过提交 mutation 来修改
- 异步操作分离:在 actions 中处理异步逻辑
- 模块化设计:按功能拆分 store 模块
6.2 性能优化
- 避免大型状态树:只存储必要的数据
- 使用严格模式开发:防止意外状态变更
- 模块懒加载:动态注册模块
// 动态注册模块
store.registerModule('dynamicModule', dynamicModule)
- 高效 getters:避免在 getters 中进行复杂计算
- 选择性订阅:使用
store.subscribeAction
替代全局订阅
6.3 开发技巧
- 使用 Vuex Devtools:实时监控状态变化
- 模块复用:创建可重用模块
- 类型安全:结合 TypeScript 增强类型提示
// 类型化的 store
interface State {
count: number
}
export default createStore<State>({
state: {
count: 0
},
// ...
})
- 错误处理:全局错误捕获
store.subscribeAction({
error: (error, action) => {
console.error(`Action 错误: ${action.type}`, error)
}
})
七、Vuex 与 Pinia 对比
特性 | Vuex | Pinia |
---|---|---|
Vue 版本 | Vue 2/3 | Vue 3 |
API 风格 | Options API | Composition API |
类型支持 | 需要额外配置 | 开箱即用 |
Devtools | 支持 | 支持 |
模块化 | 需要 namespaced | 自动命名空间 |
大小 | 约 3KB | 约 1KB |
学习曲线 | 较陡峭 | 较平缓 |
灵活性 | 中等 | 高 |
何时选择 Vuex:
- 大型复杂项目
- 需要严格的状态变更控制
- 已有 Vuex 基础的项目
- 需要完整 Devtools 支持