Appearance
Vuex 详解
1. 什么是 Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 的核心概念包括:
- State:存储应用状态
- Getters:从 State 中派生出的计算属性
- Mutations:修改 State 的唯一方式
- Actions:处理异步操作
- Modules:将状态管理分割成模块
2. 安装与基本使用
安装
bash
# 使用 npm
npm install vuex@3
# 使用 yarn
yarn add vuex@3基本使用
javascript
// 1. 导入 Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'
// 2. 注册 Vuex 插件
Vue.use(Vuex)
// 3. 创建 store
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
// 4. 在 Vue 实例中使用
new Vue({
store
}).$mount('#app')在组件中使用:
javascript
// 方式 1:直接访问
this.$store.state.count
this.$store.getters.doubleCount
// 方式 2:使用辅助函数
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment', 'decrement']),
...mapActions(['incrementAsync'])
}
}3. 核心 API
State
- 作用:存储应用的状态。
- 使用场景:用于存储需要在多个组件间共享的数据。
javascript
const store = new Vuex.Store({
state: {
count: 0,
user: {
name: 'John',
age: 30
}
}
})Getters
- 作用:从 State 中派生出的计算属性。
- 使用场景:用于处理需要基于 State 进行计算的数据。
javascript
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '学习 Vue', done: true },
{ id: 2, text: '学习 Vuex', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id)
}
}
})Mutations
- 作用:修改 State 的唯一方式。
- 特点:同步操作。
- 使用场景:用于同步修改状态。
javascript
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state, payload) {
state.count += payload || 1
}
}
})
// 调用 mutation
store.commit('increment')
store.commit('increment', 10) // 带 payload
// 或使用对象风格的提交方式
store.commit({ type: 'increment', amount: 10 })Actions
- 作用:处理异步操作。
- 特点:可以包含异步操作,通过 commit mutations 来修改状态。
- 使用场景:用于处理需要异步操作的逻辑,如 API 请求。
javascript
const store = new Vuex.Store({
state: {
posts: []
},
mutations: {
setPosts(state, posts) {
state.posts = posts
}
},
actions: {
async fetchPosts({ commit }) {
const response = await axios.get('/api/posts')
commit('setPosts', response.data)
}
}
})
// 调用 action
store.dispatch('fetchPosts')
// 带参数
store.dispatch('fetchPosts', { userId: 1 })
// 或使用对象风格的分发方式
store.dispatch({ type: 'fetchPosts', userId: 1 })Modules
- 作用:将状态管理分割成模块。
- 使用场景:用于大型应用,将状态管理按功能或业务领域分割。
javascript
const userModule = {
namespaced: true,
state: {
user: null
},
mutations: {
setUser(state, user) {
state.user = user
}
},
actions: {
async login({ commit }, credentials) {
const response = await axios.post('/api/login', credentials)
commit('setUser', response.data)
}
},
getters: {
isLoggedIn: state => !!state.user
}
}
const store = new Vuex.Store({
modules: {
user: userModule
}
})
// 访问模块状态
store.state.user.user
// 调用模块的 mutation
store.commit('user/setUser', { name: 'John' })
// 调用模块的 action
store.dispatch('user/login', { username: 'john', password: '123' })
// 访问模块的 getter
store.getters['user/isLoggedIn']4. 实现原理
核心原理
Vuex 的核心原理基于以下几点:
- 集中式存储:所有状态都存储在一个集中的 store 中。
- 响应式系统:利用 Vue 的响应式系统,当状态变化时,依赖于这些状态的组件会自动更新。
- 严格的状态变更规则:通过 mutations 来修改状态,确保状态变更可追踪。
- 异步处理:通过 actions 来处理异步操作,保持 mutations 的同步性。
源码解析
Store 构造函数
javascript
class Store {
constructor(options = {}) {
// 初始化状态
this._state = new Vue({
data: {
$$state: options.state || {}
}
})
// 初始化 mutations
this._mutations = Object.create(null)
// 初始化 actions
this._actions = Object.create(null)
// 初始化 getters
this._wrappedGetters = Object.create(null)
// 处理 modules
this._modules = new ModuleCollection(options)
// 安装模块
installModule(this, state, [], this._modules.root)
// 启用严格模式
if (options.strict) {
enableStrictMode(this)
}
}
// 获取状态
get state() {
return this._state._data.$$state
}
// 提交 mutation
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
entry.forEach(function commitIterator(handler) {
handler(payload)
})
}
// 分发 action
dispatch(type, payload) {
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
}响应式状态
Vuex 利用 Vue 的响应式系统来实现状态的响应式:
javascript
// 创建响应式状态
this._state = new Vue({
data: {
$$state: options.state || {}
}
})当状态发生变化时,Vue 的响应式系统会自动通知依赖于这些状态的组件。
Mutation 提交
Mutation 是修改状态的唯一方式,且必须是同步操作:
javascript
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
entry.forEach(function commitIterator(handler) {
handler(payload)
})
}Action 分发
Action 用于处理异步操作,最终通过 commit mutations 来修改状态:
javascript
dispatch(type, payload) {
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}模块化实现
Vuex 通过 ModuleCollection 来处理模块化:
javascript
class ModuleCollection {
constructor(rawRootModule) {
// 注册根模块
this.register([], rawRootModule, false)
}
register(path, rawModule, runtime) {
// 创建模块
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// 注册子模块
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
}5. 常见问题及解决方案
1. 状态更新但组件未更新
问题:修改状态后,组件没有更新。
原因:
- 直接修改了 state 对象,而不是通过 mutations。
- 在 state 中添加了新的属性,但没有使用 Vue.set。
解决方案:
- 始终通过 mutations 修改状态。
- 使用 Vue.set 或展开运算符添加新属性。
javascript
// 错误的做法
store.state.count = 1
// 正确的做法
store.commit('increment')
// 添加新属性的正确做法
// 在 mutation 中
Vue.set(state.user, 'age', 30)
// 或
state.user = { ...state.user, age: 30 }2. 异步操作导致的问题
问题:在 mutations 中执行异步操作,导致状态更新不可预测。
原因:mutations 应该是同步的,异步操作会导致调试困难。
解决方案:将异步操作移到 actions 中,通过 commit mutations 来修改状态。
javascript
// 错误的做法
mutations: {
async fetchData(state) {
const data = await axios.get('/api/data')
state.data = data
}
}
// 正确的做法
actions: {
async fetchData({ commit }) {
const data = await axios.get('/api/data')
commit('setData', data)
}
},
mutations: {
setData(state, data) {
state.data = data
}
}3. 模块化命名空间问题
问题:在模块化中,访问模块的状态、getters、mutations 或 actions 时出现命名冲突。
原因:模块没有启用命名空间。
解决方案:在模块中添加 namespaced: true。
javascript
const userModule = {
namespaced: true // 启用命名空间
// ...
}
// 访问时需要使用命名空间
store.commit('user/setUser', user)
store.dispatch('user/login', credentials)
store.getters['user/isLoggedIn']4. 性能问题
问题:当状态较大时,每次状态变更都会导致所有组件重新渲染。
原因:组件直接访问 $store.state,没有使用计算属性或辅助函数。
解决方案:
- 使用计算属性或
mapState辅助函数。 - 对于大型状态,考虑使用模块化。
- 使用
Object.freeze冻结不需要修改的状态。
javascript
// 推荐的做法
export default {
computed: {
...mapState({
count: state => state.count
}),
...mapGetters(['doubleCount'])
}
}5. 调试困难
问题:状态变更难以追踪,调试困难。
原因:没有使用 Vuex 的调试工具。
解决方案:
- 使用 Vue DevTools 进行调试。
- 启用严格模式,在开发环境中捕获直接修改状态的错误。
javascript
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})6. 最佳实践
1. 状态设计
- 单一数据源:所有状态都应该集中在 store 中。
- 状态扁平化:避免深层嵌套的状态结构,使用扁平化的结构。
- 使用常量:使用常量定义 mutation 和 action 类型,避免拼写错误。
javascript
// mutation-types.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
// store.js
import { INCREMENT, DECREMENT } from './mutation-types'
const store = new Vuex.Store({
mutations: {
[INCREMENT](state) {
state.count++
},
[DECREMENT](state) {
state.count--
}
}
})2. 模块化
- 按功能分割:将状态管理按功能或业务领域分割成模块。
- 启用命名空间:为每个模块启用命名空间,避免命名冲突。
- 模块嵌套:对于复杂的应用,可以嵌套模块。
3. 异步操作
- 在 actions 中处理异步:所有异步操作都应该放在 actions 中。
- 使用 async/await:使用 async/await 来处理异步操作,使代码更清晰。
- 错误处理:在 actions 中处理错误,并通过返回 Promise 传递错误。
javascript
actions: {
async fetchData({ commit }) {
try {
const response = await axios.get('/api/data')
commit('setData', response.data)
return response.data
} catch (error) {
console.error('Error fetching data:', error)
throw error
}
}
}4. 性能优化
- 使用计算属性:使用 getters 或计算属性来缓存计算结果。
- 避免直接修改状态:始终通过 mutations 修改状态。
- 使用 Vue DevTools:使用 Vue DevTools 进行性能分析。
5. 测试
- 测试 mutations:测试 mutations 是否正确修改状态。
- 测试 actions:测试 actions 是否正确处理异步操作和 commit mutations。
- 测试 getters:测试 getters 是否正确计算派生状态。
7. 完整示例
基本结构
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import posts from './modules/posts'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
loading: false
},
mutations: {
setLoading(state, status) {
state.loading = status
}
},
actions: {
setLoading({ commit }, status) {
commit('setLoading', status)
}
},
getters: {
isLoading: state => state.loading
},
modules: {
user,
posts
}
})
// store/modules/user.js
export default {
namespaced: true,
state: {
user: null,
token: null
},
mutations: {
setUser(state, user) {
state.user = user
},
setToken(state, token) {
state.token = token
},
logout(state) {
state.user = null
state.token = null
}
},
actions: {
async login({ commit }, credentials) {
try {
const response = await axios.post('/api/login', credentials)
commit('setUser', response.data.user)
commit('setToken', response.data.token)
localStorage.setItem('token', response.data.token)
return response.data
} catch (error) {
throw error
}
},
logout({ commit }) {
commit('logout')
localStorage.removeItem('token')
}
},
getters: {
isLoggedIn: state => !!state.token,
currentUser: state => state.user
}
}
// store/modules/posts.js
export default {
namespaced: true,
state: {
posts: [],
currentPost: null
},
mutations: {
setPosts(state, posts) {
state.posts = posts
},
setCurrentPost(state, post) {
state.currentPost = post
}
},
actions: {
async fetchPosts({ commit }) {
try {
const response = await axios.get('/api/posts')
commit('setPosts', response.data)
return response.data
} catch (error) {
throw error
}
},
async fetchPost({ commit }, id) {
try {
const response = await axios.get(`/api/posts/${id}`)
commit('setCurrentPost', response.data)
return response.data
} catch (error) {
throw error
}
}
},
getters: {
allPosts: state => state.posts,
currentPost: state => state.currentPost
}
}在组件中使用
javascript
<template>
<div>
<div v-if="isLoading">加载中...</div>
<div v-else>
<h1>Posts</h1>
<ul>
<li v-for="post in allPosts" :key="post.id">
{{ post.title }}
<button @click="fetchPost(post.id)">查看详情</button>
</li>
</ul>
<div v-if="currentPost">
<h2>{{ currentPost.title }}</h2>
<p>{{ currentPost.content }}</p>
</div>
<div v-if="!isLoggedIn">
<button @click="login">登录</button>
</div>
<div v-else>
<p>欢迎, {{ currentUser.name }}</p>
<button @click="logout">登出</button>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapState(['loading']),
...mapGetters('user', ['isLoggedIn', 'currentUser']),
...mapGetters('posts', ['allPosts', 'currentPost'])
},
methods: {
...mapActions(['setLoading']),
...mapActions('user', ['login', 'logout']),
...mapActions('posts', ['fetchPosts', 'fetchPost']),
async mounted() {
this.setLoading(true)
try {
await this.fetchPosts()
} catch (error) {
console.error('Error fetching posts:', error)
} finally {
this.setLoading(false)
}
}
}
}
</script>8. 总结
Vuex 是一个强大的状态管理库,通过集中式存储和严格的状态变更规则,使应用的状态管理更加可预测和可维护。
核心概念包括:
- State:存储应用状态
- Getters:派生状态的计算属性
- Mutations:同步修改状态
- Actions:处理异步操作
- Modules:模块化状态管理
实现原理基于 Vue 的响应式系统,通过集中式存储和严格的状态变更规则,确保状态的可预测性。
在使用 Vuex 时,应遵循最佳实践:
- 状态设计要合理,避免深层嵌套
- 使用模块化和命名空间
- 在 actions 中处理异步操作
- 始终通过 mutations 修改状态
- 使用计算属性和辅助函数
- 启用严格模式进行调试
通过合理使用 Vuex,可以构建出更加可维护、可扩展的 Vue 应用。