对 Vuex 的理解
理解 Vuex
Vuex 在一般的 Vue 项目中,比较常用,一般拿来实现数据的持久化,上次面试的时候面试官让我讲一讲对 Vuex 的理解(mutation 和 action 的区别),平时的话用是会用,但到了真正阐述对 Vuex 的理解的时候,却有点说不太上来......
什么是 Vuex ?
什么是 Vuex ?官方文档给出的描述是:" Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 ",其核心包括 state 、getters 、mutations 、actions 和 modules 。
Vuex 可以解决什么问题?
在一个复杂的 Vue.js 应用中,不同的组件可能需要共享相同的数据或状态,但是组件之间相互独立,没有直接的通信方式。这时,如果直接使用组件之间的传递数据来管理状态,会导致代码冗长且维护困难,同时也会破坏单向数据流的思想。此时,使用 Vuex 可以有效地解决这个问题,Vuex 通过一个集中式的状态管理机制,让多个组件共享同一个状态,并能在不同组件之间进行数据的传递和操作,使得代码更加清晰、易于维护,同时也避免了数据状态的混乱和冲突。因此,Vuex 主要解决在 Vue 项目中状态(数据)的管理和共享问题。
Vuex 的 5 个核心属性
Vuex 核心包括 state 、getters 、mutations 、actions 和 modules 。
state:
基本概念及用法
state 是存储共享状态的核心部分,类似于组件中的 data ,用于存储整个应用程序的状态。Vuex 的状态是响应式的,当 state 发生变化时,所有依赖于 state 的组件都会自动更新。state 在 Vuex 中的定义如下:
const store = new Vuex.Store({
state: {
user: {
name: 'PandaGuo',
identity: 'student'
}
}
})
在 main 文件中挂载完 Vuex 后,可以通过 this.$store.state 来访问 state 中定义的数据:
const user = this.$store.state.user;
console.log(user.name); // PandaGuo
但是在 Vue 中,不建议对 state 中的数据直接进行修改(不容易追踪数据的变化、破坏单向数据流的规则,使得应用程序变得混乱、可能导致意外情况发生),一般使用 mutation 对 state 中的数据进行修改。
mapState 辅助函数
开发过程中如果需要一次性取出 state 中的多个数据,直接写多个 this.$store.state 获取数据会显得代码冗长,写起来也不方便,这时候就可以使用 mapState 辅助函数:该方法可以将 state 中的数据映射到组件的计算属性中。具体使用方法如下:
state 中定义好数据:
const store = new Vuex.Store({
state: {
name: 'PandaGuo',
age: 22,
skill: ['Html', 'Css', 'JavaScript', 'Vue', 'Uni-app']
}
})
在全局挂载好 Vuex 的主文件之后,在其他组件引入 mapState 辅助函数,利用对象展开运算符将state混入computed对象中:
import { mapState } from "vuex";
export default {
created() {
console.log(this.name); // PandaGuo
console.log(this.age); // 22
console.log(this.skill); // [ "Html", "Css", "JavaScript", "Vue", "Uni-app" ]
},
computed: {
...mapState(['name', 'age', 'skill'])
}
}
getters:
基本概念及用法:
getters 类似于组件中的 computed ,它们可以接收 state 作为第一个参数,其它 getters 作为第二个参数,而且它们会被缓存起来,只有在依赖的状态发生变化时才会重新计算。getters 可以帮助我们派生出一些状态,以便在多个组件中共享这些状态,而不需要每次都重复计算。具体使用方法如下:
const store = new Vuex.Store({
state: {
name: 'PandaGuo',
age: 22,
skill: ['Html', 'Css', 'JavaScript', 'Vue', 'Uni-app']
},
getters: {
getCss(state) { // 接收一个参数
return state.skill[1];
},
showSkill(state, getters) { // 接收多个参数
return `${state.name} can use ${getters.getCss}`;
}
}
})
export default {
created() {
console.log(this.$store.getters.getCss); // Css
console.log(this.$store.getters.showSkill); // PandaGuo can use Css
}
}
使用 gettter 返回带参函数:
getter 还可以返回一个函数来实现给 getter 传参。然后通过参数来进行判断从而获取 state 中满足要求的状态,具体实现代码如下:
const store = new Vuex.Store({
state: {
toDoList: [
{ id: 1, task: '归纳面试题'},
{ id: 2, task: '维护旧项目'}
]
},
getters: {
getTask: (state) => (id) => {
return state.toDoList.find(item => item.id == id);
}
}
})
export default {
created() {
console.log(this.$store.getters.getTask(2)); // {id: 2, task: '维护旧项目'}
}
}
mapGetters 辅助函数:
和 mapState 辅助函数类似,mapGetters 可以将多个 getter 映射为计算属性并输入到当前组件的 computed 中,这样就可以通过直接访问计算属性,来获取各个 getter 的结果,达到简化代码效果,具体实现代码如下:
const store = new Vuex.Store({
state: {
toDoList: [
{ id: 1, task: '归纳面试题'},
{ id: 2, task: '维护旧项目'}
],
name: 'PandaGuo'
},
getters: {
getTask: (state) => (id) => {
return state.toDoList.find(item => item.id == id);
},
getName(state) {
return state.name;
}
}
})
import { mapGetters } from "vuex";
export default {
created() {
console.log(this.getTask(2)); // {id: 2, task: '维护旧项目'}
console.log(this.getName); // PandaGuo
},
computed: {
...mapGetters(['getTask', "getName"])
}
}
mutation:
基本概念及用法:
在前面提到过 state 中的数据状态不建议直接进行修改,最好的方法就是使用 mutation,mutation 是用来修改 state 状态的唯一途径,它们用于改变 state 中的数据,可以看作是一种提交更改的事件。这个事件由从 Vuex 创建的全局存储引发。
在编写 mutation 时,我们只能使用同步的代码逻辑,因为修改 state 前必须等待前一个 mutation 行完成。在应用中,我们不能直接调用 mutation ,而是要用其映射方法(例如 commit()、mapMutations() 等)进行调用。mutation 还可以传入参数,第一个参数是 state,第二个参数是载荷(payLoad),也就是额外的参数。mutation 具体使用代码如下:
const store = new Vuex.Store({
state: {
name: 'GGBond'
},
mutations: {
setName(state, payload) {
state.name = payload;
}
}
})
export default {
created() {
console.log(this.$store.state.name); // GGBond
this.$store.commit('setName', 'PandaGuo');
console.log(this.$store.state.name); // PandaGuo
}
}
mapMutations 辅助函数:
mapMutations 跟 mapState、mapGetters 一样,但有点不同的是,mapMutations 是将所有 mutations 里面的方法映射为实例 methods 里面的方法,具体实现代码如下:
const store = new Vuex.Store({
state: {
name: 'GGBond',
age: 18
},
mutations: {
setName(state, payload) {
state.name = payload;
},
setAge(state, payload) {
state.age = payload;
}
}
})
export default {
created() {
this.setName('PandaGuo');
this.setAge(22);
console.log(this.$store.state.name); // PandaGuo
console.log(this.$store.state.age); // 22
},
methods: {
...mapMutations(['setName', 'setAge'])
}
}
action:
基本概念及用法:
action 与 mutation 类似,也可以用于更新 state 中的数据状态,但是与 mutation 不同,action 是用于提交 mutation 而不是直接变更 state。通常情况,action 可以处理异步操作(不建议在 mutation 中进行异步操作), action 中默认的方法都是异步的,且返回 Promise。action 中一些常见的方法参数如下:
- context:上下文对象,包含了
commit()、dispatch()以及 getter 等操作。可以通过context.commit()来提交Mutation,也可以通过context.dispatch()触发其他Action。 - state:当前的状态对象,即
store.state。在有些情况下,我们需要直接访问state对象来进行一些操作。 - payload:负载数据,即由组件传递过来的数据,用于在
Action中更新状态。可以通过在dispatch方法中传入附加参数来传递数据。 - getter:类似于
store.getters,用于访问全局store中定义的 getter 函数。 - 其他的一些对象和方法:例如
rootState、rootGetters、rootCommit()等,用于从根state和根getters访问全局store。
action 在组件中是使用 dispatch 来进行调用,具体实现代码如下:
const store = new Vuex.Store({
state: {
name: 'GGBond',
},
mutations: {
setName(state, payload) {
state.name = payload;
}
},
actions: {
setNameAsync(context, name) {
context.commit('setName', name);
}
}
})
在组件中使用 dispatch 调用 action 中的方法:
export default {
methods: {
changeName() {
this.$store.dispatch('setNameAsync', 'PandaGuo'); // 使用 dispatch 调用
}
}
}
最后将方法绑定到按钮上,点击按钮修改数据即可完成异步修改:
<template>
{{ $store.state.name }}
<button @click="changeName">change name</button>
</template>
点击按钮进行修改,页面上的 " GGBond " 会修改成 " PandaGuo "。
mapActions 辅助函数:
mapActions 和 mapMutations 一样,它将 actions 里面的方法映射到 methods,当 action 多了可以使用 mapActions 简化操作,具体实现代码如下:
const store = new Vuex.Store({
state: {
name: 'GGBond',
gender: undefined,
age: 18
},
mutations: {
setName(state, payload) {
state.name = payload;
},
setGender(state, payload) {
state.gender = payload;
},
setAge(state, payload) {
state.age = payload;
}
},
actions: {
setNameAsync(context, name) {
context.commit('setName', name);
},
setGenderAsync(context, sex) {
context.commit('setGender', sex);
},
setAgeAsync(context, age) {
context.commit('setAge', age);
}
}
})
export default {
methods: {
...mapActions(['setNameAsync', 'setGenderAsync', 'setAgeAsync']),
changeInfo() {
this.setNameAsync('PandaGuo');
this.setGenderAsync('男');
this.setAgeAsync(22);
}
}
}
<template>
{{ $store.state.name }}
{{ $store.state.gender }}
{{ $store.state.age }}
<button @click="changeInfo">change name</button>
</template>
最终点击按钮会异步修改页面上的相关数据: " PandaGuo 男 22 "。
module:
基本概念及用法:
企业级大项目通常需要存储更多的数据状态,如果都存储到一个 store 对象会显得臃肿且不易维护,这时候就可以使用 module 将 store 分割成小模块,这些模块在需要使用时被动态添加到 Vuex Store 当中。具体实现代码如下:
const userModule = {
namespaced: true, // 告诉 Vuex 把 userModule 当做带命名空间的模块来处理
state: {
userInfo: {
name: 'PandaGuo',
age: 22,
gender: 'male'
}
},
mutations: {
setName(state, name) {
state.userInfo.name = name;
},
setAge(state, age) {
state.userInfo.age = age;
},
setGender(state, gender) {
state.userInfo.gender = gender;
}
},
actions: {
fetchUserInfo(context) {
// 模拟异步请求用户信息的过程
setTimeout(() => {
context.commit('setName', 'Tom');
context.commit('setAge', 18);
context.commit('setGender', 'female');
}, 1000);
}
},
getters: {
userInfoStr(state) {
return `Name: ${state.userInfo.name}, Age: ${state.userInfo.age}, Gender: ${state.userInfo.gender}`;
}
}
}
const store = new Vuex.Store({
modules: {
user: userModule // 将上述 userModule 注册到 store 的 modules 属性中。
}
});
在组件具体使用某一个模块的方法:
export default {
methods: {
...mapActions('user', ['fetchUserInfo'])
},
created() {
this.fetchUserInfo();
}
}
action 与 mutation 的区别:
上次面试官问了我这个问题,我只能答出同步和异步的区别,后面再挖掘就答不上来了......
- action 提交的是 mutation,而不是直接变更状态,mutation 可以直接变更状态;
- action 可以包含任意的异步操作,mutation 只能进行同步操作;
- action 使用的是 dispatch 进行提交,而 mutation 使用的是 commit 进行提交;
- mutation 第一个参数是 state ,而 action 第一个参数是 context(上下文对象),其中包含了:
{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters // 等同于 `store.getters`,只存在于模块中
}
mutation 中是否可以进行异步操作?
上次面试官问了我这个问题,一开始我的回答是否,mutation 只能进行同步操作,但后面面试官说其实是可以的,只是不建议这么做。在官方文档中有提到这一点:" 在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务 "
参考资料:
转载自:https://juejin.cn/post/7242499021170884667