# Vuex状态管理指南
Vuex是一个专为Vue.js应用程序开发的状态管理模式+库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。本文将深入介绍Vuex的核心概念、工作原理和最佳实践。
# 什么是状态管理
在Vue应用中,组件之间共享状态(数据)的几种常见方式:
- Props向下传递 - 父组件向子组件传递数据
- 事件向上传递 - 子组件通过事件向父组件发送消息
- 事件总线/发布订阅 - 任意组件间通信
- 依赖注入(provide/inject) - 深层组件通信
- 中央存储(Vuex) - 集中管理共享状态
随着应用复杂度增加,组件层级变深,简单的共享方式变得难以维护。Vuex为解决这一问题而生。
# Vuex核心概念
# 1. State - 单一状态树
Vuex使用单一状态树,即一个对象包含应用所有的状态:
const store = new Vuex.Store({
state: {
count: 0,
todos: [],
user: null,
products: []
}
})
2
3
4
5
6
7
8
在组件中访问状态:
<template>
<div>计数: {{ count }}</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
使用mapState辅助函数简化访问:
import { mapState } from 'vuex'
export default {
computed: {
// 映射this.count为store.state.count
...mapState(['count', 'todos']),
// 使用别名
...mapState({
myCount: 'count',
todoList: 'todos'
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2. Getters - 派生状态
Getters用于从store中的state派生出一些状态,类似于计算属性:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '学习Vue', done: true },
{ id: 2, text: '学习Vuex', done: false },
{ id: 3, text: '构建应用', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
// 支持传参的getter
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id)
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在组件中使用:
<template>
<div>
<p>已完成任务: {{ doneTodosCount }}</p>
<p>任务1: {{ todo1 }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
// 映射
...mapGetters(['doneTodos', 'doneTodosCount']),
// 使用别名
...mapGetters({
completedTasks: 'doneTodos'
}),
// 使用带参数的getter
todo1() {
return this.$store.getters.getTodoById(1)
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3. Mutations - 状态变更
更改Vuex的store中的状态的唯一方法是提交mutation:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 简单变更
increment(state) {
state.count++
},
// 接收参数(载荷payload)
incrementBy(state, n) {
state.count += n
},
// 接收对象参数
updateUser(state, userData) {
state.user = { ...state.user, ...userData }
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
提交mutation:
// 直接提交
store.commit('increment')
// 带参数
store.commit('incrementBy', 10)
// 对象风格提交
store.commit({
type: 'incrementBy',
amount: 10
})
2
3
4
5
6
7
8
9
10
11
在组件中使用:
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'increment', // 映射this.increment()为this.$store.commit('increment')
'incrementBy'
]),
// 使用别名
...mapMutations({
add: 'increment'
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意事项:
- Mutation必须是同步函数
- 尽量保持mutation简单、明确
- 遵循Vue的响应规则(对象添加属性、数组项更改等)
# 4. Actions - 异步操作
Actions类似于mutations,不同在于:
- Actions提交的是mutation,而不是直接变更状态
- Actions可以包含任意异步操作
const store = new Vuex.Store({
state: {
todos: []
},
mutations: {
setTodos(state, todos) {
state.todos = todos
},
addTodo(state, todo) {
state.todos.push(todo)
}
},
actions: {
// 简单action
fetchTodos({ commit }) {
return fetch('/api/todos')
.then(response => response.json())
.then(todos => {
commit('setTodos', todos)
})
},
// 使用async/await
async fetchTodosAsync({ commit }) {
try {
const response = await fetch('/api/todos')
const todos = await response.json()
commit('setTodos', todos)
return todos
} catch (error) {
console.error('获取任务失败:', error)
throw error
}
},
// 接收参数
async addTodoAsync({ commit }, todoText) {
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: todoText, done: false })
})
const newTodo = await response.json()
commit('addTodo', newTodo)
return newTodo
} catch (error) {
console.error('添加任务失败:', error)
throw error
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
分发action:
// 分发
store.dispatch('fetchTodos')
// 带参数分发
store.dispatch('addTodoAsync', '学习Vuex')
// 对象风格分发
store.dispatch({
type: 'addTodoAsync',
text: '学习Vuex'
})
2
3
4
5
6
7
8
9
10
11
在组件中使用:
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'fetchTodos', // 映射this.fetchTodos()为this.$store.dispatch('fetchTodos')
'addTodoAsync'
]),
// 使用别名
...mapActions({
fetch: 'fetchTodos'
}),
// 处理action返回的Promise
async loadData() {
try {
await this.fetchTodos()
this.isLoaded = true
} catch (error) {
this.error = error.message
}
}
},
created() {
this.loadData()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 5. Modules - 模块化状态管理
当应用变得复杂时,store对象可能变得相当臃肿。Vuex允许我们将store分割成模块:
// 用户模块
const user = {
namespaced: true,
state: {
profile: null,
token: null
},
getters: {
isLoggedIn: state => !!state.token
},
mutations: {
setProfile(state, profile) {
state.profile = profile
},
setToken(state, token) {
state.token = token
}
},
actions: {
async login({ commit }, credentials) {
try {
const { token, user } = await authService.login(credentials)
commit('setToken', token)
commit('setProfile', user)
return user
} catch (error) {
throw error
}
},
logout({ commit }) {
commit('setToken', null)
commit('setProfile', null)
}
}
}
// 商品模块
const products = {
namespaced: true,
state: {
items: [],
loading: false
},
getters: {
availableProducts: state => state.items.filter(p => p.inventory > 0)
},
mutations: {
setProducts(state, products) {
state.items = products
},
setLoading(state, isLoading) {
state.loading = isLoading
}
},
actions: {
async fetchProducts({ commit }) {
commit('setLoading', true)
try {
const products = await productService.getProducts()
commit('setProducts', products)
} finally {
commit('setLoading', false)
}
}
}
}
// 根store
const store = new Vuex.Store({
modules: {
user,
products
}
})
// 访问模块状态
console.log(store.state.user.profile) // 用户信息
console.log(store.state.products.items) // 商品列表
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
在组件中使用命名空间模块:
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// 使用命名空间访问
...mapState('user', ['profile']),
...mapGetters('user', ['isLoggedIn']),
...mapState('products', {
productList: 'items',
isLoading: 'loading'
}),
...mapGetters('products', ['availableProducts'])
},
methods: {
...mapActions('user', ['login', 'logout']),
...mapActions('products', ['fetchProducts']),
// 或者直接通过store访问
loginUser() {
this.$store.dispatch('user/login', {
username: this.username,
password: this.password
})
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Vuex插件与高级特性
# 插件
Vuex插件就是一个函数,接收store作为唯一参数:
// 简单的日志插件
const logPlugin = store => {
// 在每个mutation完成后调用
store.subscribe((mutation, state) => {
console.log('mutation:', mutation.type, mutation.payload)
console.log('new state:', state)
})
}
// 持久化插件
const persistPlugin = store => {
// 从localStorage恢复状态
const savedState = localStorage.getItem('vuex-state')
if (savedState) {
store.replaceState(JSON.parse(savedState))
}
// 订阅状态变更
store.subscribe((mutation, state) => {
localStorage.setItem('vuex-state', JSON.stringify(state))
})
}
// 使用插件
const store = new Vuex.Store({
// ...
plugins: [logPlugin, persistPlugin]
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 严格模式
严格模式下,任何非mutation方式修改Vuex状态都会抛出错误:
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
2
3
4
注意: 不要在生产环境启用严格模式,会影响性能。
# Vuex与Vue组合式API
在Vue 3的组合式API中使用Vuex:
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<button @click="increment">+</button>
<button @click="fetchData">Load Data</button>
</div>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
// 获取状态
const count = computed(() => store.state.count)
// 获取getters
const double = computed(() => store.getters.doubleCount)
// 方法
const increment = () => store.commit('increment')
const fetchData = () => store.dispatch('fetchData')
return {
count,
double,
increment,
fetchData
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 实际应用示例
让我们构建一个包含身份验证和产品管理的电子商务应用的Vuex store:
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
import products from './modules/products'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth,
products,
cart
},
state: {
notificationMessage: null,
isLoading: false
},
mutations: {
setNotification(state, message) {
state.notificationMessage = message
},
clearNotification(state) {
state.notificationMessage = null
},
setLoading(state, status) {
state.isLoading = status
}
},
actions: {
notify({ commit }, message) {
commit('setNotification', message)
setTimeout(() => {
commit('clearNotification')
}, 3000)
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// store/modules/auth.js
import authApi from '@/api/auth'
export default {
namespaced: true,
state: {
user: null,
token: localStorage.getItem('token') || null
},
getters: {
isAuthenticated: state => !!state.token,
currentUser: state => state.user
},
mutations: {
setToken(state, token) {
state.token = token
if (token) {
localStorage.setItem('token', token)
} else {
localStorage.removeItem('token')
}
},
setUser(state, user) {
state.user = user
}
},
actions: {
async login({ commit, dispatch }, credentials) {
try {
const { token, user } = await authApi.login(credentials)
commit('setToken', token)
commit('setUser', user)
dispatch('notify', '登录成功', { root: true })
return user
} catch (error) {
dispatch('notify', `登录失败: ${error.message}`, { root: true })
throw error
}
},
async register({ commit, dispatch }, userData) {
try {
const { token, user } = await authApi.register(userData)
commit('setToken', token)
commit('setUser', user)
dispatch('notify', '注册成功', { root: true })
return user
} catch (error) {
dispatch('notify', `注册失败: ${error.message}`, { root: true })
throw error
}
},
async fetchUserProfile({ commit, state, dispatch }) {
if (!state.token) return null
try {
const user = await authApi.getProfile()
commit('setUser', user)
return user
} catch (error) {
dispatch('notify', `获取用户信息失败: ${error.message}`, { root: true })
if (error.status === 401) {
dispatch('logout')
}
throw error
}
},
logout({ commit }) {
commit('setToken', null)
commit('setUser', null)
commit('cart/clearCart', null, { root: true })
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
使用示例:
<template>
<div class="login-form">
<h2>用户登录</h2>
<div v-if="error" class="error">{{ error }}</div>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="email">邮箱</label>
<input
id="email"
v-model="email"
type="email"
required
/>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
id="password"
v-model="password"
type="password"
required
/>
</div>
<button
type="submit"
:disabled="isLoading"
>
{{ isLoading ? '登录中...' : '登录' }}
</button>
</form>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
email: '',
password: '',
error: null
}
},
computed: {
...mapState({
isLoading: state => state.isLoading
})
},
methods: {
...mapActions('auth', ['login']),
async handleLogin() {
this.error = null
try {
await this.login({
email: this.email,
password: this.password
})
this.$router.push('/dashboard')
} catch (err) {
this.error = err.message || '登录失败'
}
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 性能优化
随着应用规模增长,Vuex store可能变得庞大,影响性能。以下是一些优化策略:
- 模块动态注册 - 按需加载模块
- 规范化状态 - 避免重复和嵌套数据
- 选择性订阅 - 只监听需要的状态
- 计算属性缓存 - 利用Vue的计算属性缓存
// 动态注册模块
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 动态注册auth模块
if (!store.hasModule('auth')) {
store.registerModule('auth', authModule)
}
}
next()
})
2
3
4
5
6
7
8
9
10
# 与Composition API结合
Vue 3的组合式API提供了更灵活的方式使用Vuex:
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// 获取state
const count = computed(() => store.state.count)
// 获取getters
const doubleCount = computed(() => store.getters.doubleCount)
// 提交mutation
const increment = () => store.commit('increment')
// 分发action
const fetchData = () => store.dispatch('fetchData')
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 从Vuex迁移到Pinia
随着Vue 3的普及,官方推荐的状态管理方案已从Vuex转向Pinia。Pinia被视为Vuex 5的雏形,提供了更简单的API、更好的TypeScript支持和更轻量的包体积。
# Pinia与Vuex的主要区别
- 更简单的API: 没有mutations,只有state、getters和actions
- 更好的TypeScript支持: 自动类型推导,无需额外类型声明
- 去除命名空间: 使用store实例直接导入和使用
- DevTools支持: 改进的开发者工具体验
# 简单迁移示例
// Vuex store
export default {
namespaced: true,
state: {
counter: 0
},
getters: {
doubleCount: (state) => state.counter * 2
},
mutations: {
increment(state) {
state.counter++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
}
}
// 同等功能的Pinia store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
counter: 0
}),
getters: {
doubleCount: (state) => state.counter * 2
},
actions: {
increment() {
this.counter++
},
incrementAsync() {
setTimeout(() => this.increment(), 1000)
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 学习资源
- Vuex官方文档 (opens new window)
- Vuex GitHub仓库 (opens new window)
- Pinia官方文档 (opens new window)(Vue 3推荐状态管理库)
- Vuex or Pinia? (opens new window) - Vue团队对状态管理的推荐
- Vuex可视化教程 (opens new window)
# 结语
Vuex是Vue生态系统中不可或缺的部分,为复杂应用提供了可靠的状态管理方案。随着对其概念的理解和实践,您将能够构建更加健壮和可维护的Vue应用。在我在字节跳动的工作经验中,正确使用Vuex使我们能够轻松管理复杂应用中的共享状态,显著提升了团队协作效率和代码质量。