Vuex的基本用法详解
Vuex是Vue.js应用的状态管理模式,用于集中管理组件共享的状态,解决多组件间状态传递、共享的复杂问题。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。下面我们将从核心概念到实际用法逐步讲解Vuex的基本使用。
一、Vuex的核心概念
在使用Vuex之前,需要先了解它的几个核心概念,这些概念是理解和使用Vuex的基础:
- State:存储应用的状态数据,相当于组件中的data,是所有组件共享的数据源。
- Getter:类似于组件的计算属性,用于从State中派生出一些状态,比如对State数据进行过滤、计算等操作。
- Mutation:唯一修改State的方式,必须是同步函数,通过提交Mutation来改变状态。
- Action:类似于Mutation,但可以包含异步操作,不能直接修改State,需要提交Mutation来间接修改状态。
- Module:当应用状态比较复杂时,可以将Store分割成模块,每个模块拥有自己的State、Mutation、Action、Getter。
二、安装与引入Vuex
如果是通过Vue CLI创建的项目,可以在项目初始化时选择安装Vuex,也可以后续手动安装。手动安装命令如下:
# 使用npm安装 npm install vuex --save # 使用yarn安装 yarn add vuex
安装完成后,在项目中引入并使用Vuex:
// 引入Vue和Vuex
import Vue from 'vue'
import Vuex from 'vuex'
// 使用Vuex插件
Vue.use(Vuex)
// 创建Store实例
const store = new Vuex.Store({
// 后续在这里配置State、Mutation等核心内容
})
// 将Store挂载到Vue实例上
new Vue({
store,
render: h => h(App)
}).$mount('#app')三、State的基本用法
State是存储共享状态的地方,我们可以在Store中定义State,然后在组件中访问这些状态。
// 创建Store时配置State
const store = new Vuex.Store({
state: {
// 定义一个计数器状态
count: 0,
// 定义一个用户列表状态
userList: []
}
})在组件中访问State有两种常见方式:
- 通过
this.$store.state.状态名直接访问,这种方式适合在组件模板或逻辑中直接使用。 - 使用
mapState辅助函数将State映射为组件的计算属性,简化访问逻辑。
下面是使用mapState的示例:
import { mapState } from 'vuex'
export default {
computed: {
// 数组形式映射,当计算属性名和State中的状态名一致时使用
...mapState(['count', 'userList']),
// 对象形式映射,可以自定义计算属性名,或者处理复杂的映射逻辑
...mapState({
// 自定义计算属性名为myCount,映射到state.count
myCount: state => state.count,
// 当计算属性名和State状态名一致时,也可以简写为字符串
userList: 'userList'
})
}
}四、Mutation的基本用法
Mutation是唯一修改State的方式,每个Mutation都有一个字符串的事件类型和一个回调函数,回调函数的第一个参数是State,第二个参数是额外传入的参数(称为载荷)。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
// 定义一个增加计数的Mutation,载荷是增加的数量,默认是1
increment(state, payload = 1) {
state.count += payload
},
// 定义一个减少计数的Mutation
decrement(state, payload = 1) {
state.count -= payload
},
// 定义一个设置用户列表的Mutation
setUserList(state, list) {
state.userList = list
}
}
})在组件中提交Mutation有两种方式:
- 通过
this.$store.commit('Mutation名称', 载荷)提交。 - 使用
mapMutations辅助函数将Mutations映射为组件的methods,之后直接调用方法即可。
下面是使用mapMutations的示例:
import { mapMutations } from 'vuex'
export default {
methods: {
// 映射Mutations为组件方法,数组形式适合Mutation名称和组件方法名一致的情况
...mapMutations(['increment', 'decrement', 'setUserList']),
// 对象形式可以自定义组件方法名
...mapMutations({
add: 'increment', // 组件方法add对应Mutation increment
sub: 'decrement' // 组件方法sub对应Mutation decrement
}),
handleAdd() {
// 调用映射后的方法,传入载荷
this.increment(2)
// 也可以直接通过commit提交
// this.$store.commit('increment', 2)
}
}
}五、Action的基本用法
Action用于处理异步操作,它不能直接修改State,需要通过提交Mutation来间接修改状态。Action的回调函数接收一个与Store实例具有相同方法和属性的context对象,因此可以调用context.commit提交Mutation,也可以通过context.state和context.getters访问State和Getter。
const store = new Vuex.Store({
state: {
userList: []
},
mutations: {
setUserList(state, list) {
state.userList = list
}
},
actions: {
// 定义一个获取用户列表的异步Action
async fetchUserList(context) {
try {
// 模拟异步请求,实际项目中替换为真实的接口请求
const response = await new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
])
}, 1000)
})
// 提交Mutation修改State
context.commit('setUserList', response)
} catch (error) {
console.error('获取用户列表失败', error)
}
}
}
})在组件中分发Action有两种方式:
- 通过
this.$store.dispatch('Action名称', 载荷)分发。 - 使用
mapActions辅助函数将Actions映射为组件的methods,之后直接调用方法即可。
下面是使用mapActions的示例:
import { mapActions } from 'vuex'
export default {
methods: {
// 映射Actions为组件方法
...mapActions(['fetchUserList']),
// 对象形式自定义方法名
...mapActions({
getUser: 'fetchUserList'
}),
async loadUserList() {
// 调用映射后的方法
await this.fetchUserList()
// 也可以直接通过dispatch分发
// await this.$store.dispatch('fetchUserList')
}
},
created() {
// 组件创建时触发获取用户列表的Action
this.loadUserList()
}
}六、Getter的基本用法
Getter用于对State中的数据进行加工处理,类似于组件的计算属性,Getter的返回值会根据它的依赖被缓存,只有依赖值发生改变时才会重新计算。
const store = new Vuex.Store({
state: {
count: 0,
userList: [
{ id: 1, name: '张三', age: 20 },
{ id: 2, name: '李四', age: 25 },
{ id: 3, name: '王五', age: 18 }
]
},
getters: {
// 获取count的两倍值
doubleCount(state) {
return state.count * 2
},
// 获取年龄大于20的用户列表
adultUsers(state) {
return state.userList.filter(user => user.age > 20)
},
// Getter也可以接收其他Getter作为第二个参数
adultUserCount(state, getters) {
return getters.adultUsers.length
}
}
})在组件中访问Getter有两种方式:
- 通过
this.$store.getters.getter名称直接访问。 - 使用
mapGetters辅助函数将Getters映射为组件的计算属性。
下面是使用mapGetters的示例:
import { mapGetters } from 'vuex'
export default {
computed: {
// 映射Getters为计算属性
...mapGetters(['doubleCount', 'adultUsers', 'adultUserCount']),
// 对象形式自定义计算属性名
...mapGetters({
twoCount: 'doubleCount'
})
}
}七、Module的基本用法
当应用的状态比较复杂时,将所有状态、Mutation、Action、Getter都写在一个Store里会导致代码难以维护,这时候可以使用Module将Store分割成多个模块,每个模块拥有自己的State、Mutation、Action、Getter。
// 定义用户模块
const userModule = {
// 开启命名空间,避免不同模块的Mutation、Action、Getter名称冲突
namespaced: true,
state: {
userInfo: null
},
mutations: {
setUserInfo(state, info) {
state.userInfo = info
}
},
actions: {
async fetchUserInfo({ commit }) {
// 模拟异步获取用户信息
const info = await new Promise(resolve => {
setTimeout(() => {
resolve({ id: 1, name: '张三', age: 20 })
}, 1000)
})
commit('setUserInfo', info)
}
},
getters: {
userName(state) {
return state.userInfo ? state.userInfo.name : '未登录'
}
}
}
// 定义计数模块
const countModule = {
namespaced: true,
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
}
// 创建根Store,引入模块
const store = new Vuex.Store({
modules: {
user: userModule,
count: countModule
}
})在使用带命名空间的模块时,访问对应的状态、提交Mutation、分发Action、访问Getter都需要带上模块名称:
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
computed: {
// 访问模块中的State,需要指定模块名
...mapState('user', ['userInfo']),
...mapState('count', ['count']),
// 访问模块中的Getter,需要指定模块名
...mapGetters('user', ['userName']),
...mapGetters('count', ['doubleCount'])
},
methods: {
// 提交模块中的Mutation,需要指定模块名
...mapMutations('count', ['increment']),
// 分发模块中的Action,需要指定模块名
...mapActions('user', ['fetchUserInfo'])
},
created() {
// 调用模块的Action
this.fetchUserInfo()
}
}八、完整使用示例
下面是一个简单的计数器示例,整合了State、Mutation、Action、Getter的使用:
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state, payload = 1) {
state.count += payload
},
decrement(state, payload = 1) {
state.count -= payload
}
},
actions: {
async asyncIncrement({ commit }, payload) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 500))
commit('increment', payload)
},
async asyncDecrement({ commit }, payload) {
await new Promise(resolve => setTimeout(resolve, 500))
commit('decrement', payload)
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})对应的组件代码:
<template>
<div class="counter">
<p>当前计数:{{ count }}</p>
<p>计数的两倍:{{ doubleCount }}</p>
<button @click="increment(1)">同步加1</button>
<button @click="decrement(1)">同步减1</button>
<button @click="asyncIncrement(2)">异步加2</button>
<button @click="asyncDecrement(2)">异步减2</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'Counter',
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment', 'decrement']),
...mapActions(['asyncIncrement', 'asyncDecrement'])
}
}
</script>这个示例中,同步修改计数直接调用映射后的Mutation方法,异步修改计数调用映射后的Action方法,计数和派生出的两倍计数都通过辅助函数映射为组件的计算属性,直接在模板中使用。