Appearance
Vuex 状态管理
本文介绍Vuex的核心概念、使用方法和最佳实践。Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么是Vuex?
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
为什么需要Vuex?
当应用变得复杂时,组件间共享状态的方式会变得混乱和难以维护。Vuex提供了一种集中式的状态管理方案,解决以下问题:
- 多个组件共享同一状态时,难以跟踪状态的变化路径
- 组件间通信变得复杂,特别是在多层嵌套组件中
- 难以调试,无法追踪状态变化的历史
Vuex的核心概念
- State:存储应用状态的单一对象
- Getter:从state中派生出的计算属性
- Mutation:修改state的唯一方法,必须是同步函数
- Action:可以包含异步操作,通过提交mutation来修改state
- Module:将store分割成模块,便于管理大型应用
安装与配置
安装Vuex
bash
# 使用npm
npm install vuex --save
# 使用yarn
yarn add vuex基本配置
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
// 状态数据
},
mutations: {
// 修改状态的方法
},
actions: {
// 异步操作
},
getters: {
// 计算属性
},
modules: {
// 模块
}
})
export default store在Vue实例中使用
javascript
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')State
State是存储应用状态的单一对象,是应用中所有组件的数据源。
定义State
javascript
const store = new Vuex.Store({
state: {
count: 0,
user: {
name: 'John',
age: 30
},
todos: [
{ id: 1, text: 'Learn Vuex', done: true },
{ id: 2, text: 'Build an app', done: false }
]
}
})访问State
在组件中访问
javascript
// 选项式API
this.$store.state.count
// 组合式API
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
console.log(store.state.count)
}
}使用mapState辅助函数
javascript
import { mapState } from 'vuex'
export default {
computed: {
// 对象展开运算符
...mapState([
'count', // 映射 this.count 为 store.state.count
'user'
]),
// 重命名
...mapState({
myCount: 'count', // 映射 this.myCount 为 store.state.count
myUser: state => state.user
})
}
}Getter
Getter用于从state中派生出一些状态,类似于组件中的计算属性。
定义Getter
javascript
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Learn Vuex', done: true },
{ id: 2, text: 'Build an app', done: false }
]
},
getters: {
// 基础用法
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// 带参数的getter
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
},
// 使用其他getter
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})访问Getter
javascript
// 直接访问
this.$store.getters.doneTodos
// 使用mapGetters辅助函数
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodos',
'doneTodosCount'
]),
// 重命名
...mapGetters({
myDoneTodos: 'doneTodos'
})
}
}Mutation
Mutation是修改state的唯一方法,必须是同步函数。
定义Mutation
javascript
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
// 基础用法
increment(state) {
// 变更状态
state.count++
},
// 带参数的mutation
incrementBy(state, payload) {
state.count += payload.amount
},
// 通常使用对象风格的提交方式
setUser(state, user) {
state.user = user
}
}
})提交Mutation
javascript
// 提交不带参数的mutation
this.$store.commit('increment')
// 提交带参数的mutation(载荷方式)
this.$store.commit('incrementBy', { amount: 10 })
// 对象风格的提交方式
this.$store.commit({
type: 'incrementBy',
amount: 10
})
// 使用mapMutations辅助函数
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'increment', // 映射 this.increment() 为 this.$store.commit('increment')
'setUser'
]),
...mapMutations({
add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
})
}
}Mutation的注意事项
- Mutation必须是同步函数
- 最好提前在你的store中初始化好所有所需属性
- 使用常量替代Mutation事件类型可以使代码更清晰
javascript
// mutation-types.js
export const INCREMENT = 'increment'
export const SET_USER = 'setUser'
// store/index.js
import { INCREMENT, SET_USER } from './mutation-types'
const store = new Vuex.Store({
mutations: {
[INCREMENT](state) {
// ...
},
[SET_USER](state, user) {
// ...
}
}
})Action
Action类似于Mutation,但可以包含异步操作,通过提交Mutation来修改state。
定义Action
javascript
const store = new Vuex.Store({
state: {
count: 0,
user: null
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
}
},
actions: {
// 基础用法
increment(context) {
context.commit('increment')
},
// 解构参数
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
// 带参数的action
incrementBy({ commit }, payload) {
commit('incrementBy', payload)
},
// 异步数据获取
fetchUser({ commit }, userId) {
return new Promise((resolve, reject) => {
api.getUser(userId)
.then(user => {
commit('setUser', user)
resolve(user)
})
.catch(error => {
reject(error)
})
})
}
}
})分发Action
javascript
// 分发action
this.$store.dispatch('increment')
// 分发带参数的action
this.$store.dispatch('incrementBy', { amount: 10 })
// 分发异步action
this.$store.dispatch('fetchUser', 123)
.then(user => {
console.log(user)
})
// 使用mapActions辅助函数
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment', // 映射 this.increment() 为 this.$store.dispatch('increment')
'fetchUser'
]),
...mapActions({
add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
})
}
}Module
Module用于将store分割成模块,每个模块拥有自己的state、mutation、action、getter,甚至可以嵌套子模块。
定义Module
javascript
// store/modules/cart.js
const cart = {
namespaced: true, // 开启命名空间
state: () => ({ items: [] }),
mutations: {
addItem(state, item) {
state.items.push(item)
}
},
actions: {
addItemAsync({ commit }, item) {
setTimeout(() => {
commit('addItem', item)
}, 1000)
}
},
getters: {
itemCount(state) {
return state.items.length
}
}
}
// store/modules/user.js
const user = {
namespaced: true,
state: () => ({ name: '' }),
mutations: {
setName(state, name) {
state.name = name
}
}
}
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
cart,
user
}
})使用Module
javascript
// 访问模块状态
this.$store.state.cart.items
// 提交模块mutation
this.$store.commit('cart/addItem', { id: 1, name: 'Product' })
// 分发模块action
this.$store.dispatch('cart/addItemAsync', { id: 1, name: 'Product' })
// 访问模块getter
this.$store.getters['cart/itemCount']
// 使用map辅助函数时指定模块
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('cart', [
'items'
])
},
methods: {
...mapActions('cart', [
'addItemAsync'
])
}
}
// 或者使用createNamespacedHelpers
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('cart')
export default {
computed: {
...mapState([
'items'
])
},
methods: {
...mapActions([
'addItemAsync'
])
}
}高级特性
插件
Vuex允许你通过插件来扩展其功能。插件通常是一个函数,它接收store作为唯一参数。
javascript
// 日志插件
const logger = store => {
// 当store初始化后调用
store.subscribe((mutation, state) => {
console.log('mutation', mutation)
console.log('state', state)
})
}
const store = new Vuex.Store({
// ...
plugins: [logger]
})严格模式
在严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
javascript
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})表单处理
在Vuex中处理表单输入时,可以使用v-model结合computed的getter和setter:
html
<input v-model="username">javascript
export default {
computed: {
username: {
get() {
return this.$store.state.user.name
},
set(value) {
this.$store.commit('setUserName', value)
}
}
}
}最佳实践
状态设计
- 单一数据源:应用的所有状态都应集中在一个store中
- 状态是只读的:唯一修改状态的方式是提交mutation
- 使用常量命名mutation:便于协作和代码维护
- 模块化:大型应用使用modules分割状态
项目结构
store/
├── index.js # 入口文件,创建store
├── mutation-types.js # mutation类型常量
├── actions.js # 根级action
├── mutations.js # 根级mutation
├── getters.js # 根级getter
└── modules/ # 模块目录
├── cart.js
└── user.js性能优化
- 使用mapState、mapGetters等辅助函数减少重复代码
- 对于大型应用,考虑使用模块的命名空间
- 避免在getter中执行复杂计算,必要时进行缓存
- 在开发环境使用严格模式,生产环境禁用
调试
- 使用Vue Devtools:可以时间旅行调试,追踪状态变化
- 开发环境启用严格模式
- 使用日志插件记录mutation和状态变化
与Pinia的对比
Vuex 4是Vue 3兼容版本,而Pinia是Vue官方推荐的新一代状态管理库,具有以下优势:
- 更简洁的API:不需要嵌套模块,不需要Mutation
- 更好的TypeScript支持
- 更轻量的体积
- 更简单的组合式API集成
对于新项目,推荐使用Pinia;对于现有Vuex项目,可以继续使用或逐步迁移到Pinia。
总结
Vuex提供了一种集中式状态管理方案,通过State、Getter、Mutation、Action和Module等核心概念,帮助我们更好地管理应用状态。合理使用Vuex可以使应用状态变化更加可预测、可调试,同时提高代码的可维护性和复用性。在实际开发中,应根据项目规模和需求选择合适的状态管理方案,并遵循最佳实践进行开发。