🌩 Vuex 状态管理
# Vuex 的理解
- Vuex 的概念: 专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中的多个组件的共享状态进行集中式的管理(读 / 写操作),也是组件之间通信的一种方式,并且适用于任意组件间的通信。
🔗 相关链接:
- 使用全局事件总线实现多组件共享数据:不只是读、还要写的操作时,就显得有点繁杂。
- 使用 Vuex 多组件共享数据:读写可以在双向操作。
- 什么时候使用 Vuex :共享一个状态。
- 多个组件依赖与同一个状态。
- 来自不同的组件的行为需要变更同一个状态。
# Vuex 原理
Vuex 三个重要组成部分:
- Actions:动作行为。
- Mutations:加工维护。
- State:状态(数据) ,数据交给 State 保管,可以包含许多数据关键词。
可以在图中了解 Vuex 的工作流程。
三个组成部分可以看成是三个对象中存储数据。
Actions 与后端接口相关,当组件进行的动作的数据需要通过 AJAX 请求获取,即不知道动作的数据。实际上,当组件知道进行的动作和数据,Vue 会直接 Commit 到 Mutations,即 Actions 可以省略。
Mutations 是实际加工状态的地方。
Vuex 基于 store 工作。例如,
store.dispatch
、store.commit
、store.mutate
,在任意组件中都能调用。
# Vuex 的使用
# Vuex 的开发环境
- 安装 Vuex 3:
$ npm i vuex@3
提示
在 2022 年 2 月 7 日,Vue 3 成为了默认的版本(即执行 npm i vue
安装的版本是 Vue 3),Vuex 也更新到了 Vuex 4 版本,而 Vue 4 只能在 Vue 3 中运行(所以执行 npm i vuex
安装的是 Vuex 4)。所以,在目前基于 Vue 2 版本的学习,我们要安装可以基于 Vue 2 运行的 Vuex 3。
- 创建 Vuex 的操作文件
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
提示
注意:由于 store
的引入不能晚于引入插件 Vuex
,所以现在操作文件中引入 Vue
并且引入插件 Vuex
,然后在入口文件就直接引入 store
配置项即可。
- 在入口文件
main.js
中创建vm
实例时传入store
配置项:
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
2
3
4
5
6
7
8
9
10
11
# Vuex 的基本使用
- 在
store
操作文件index.js
中初始化数据,配置actions
和mutations
的方法:
// 响应组件中的动作
const actions = {
add(context,value){
context.commit('ADD',value)
},
}
// 执行动作的实际操作
const mutations = {
ADD(state,value){
state.sum += value
}
}
//初始化数据
const state = {
sum:0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
提示
actions
中定义的方法有两个参数:context
:上下文相关的内容,一般提供给动作中的方法中所用到的内容。(可以看作是store
的部分内容)注意:包括
dispatch
:可以用来在方法中调取在actions
中的其他方法。实际开发中没有像例子那么简单的业务逻辑,甚至如此简单的业务逻辑可以在组件中直接完成,但是一旦有繁杂的业务逻辑此时actions
的作用就显现出来,并且可以在actions
中定义多个方法包含多种业务逻辑提供其中一个方法使用dispatch
调取。value
:组件传递给 Vuex 要进行操作的数据。在actions
中可对此进行判断等业务逻辑。
mutations
中定义的方法有两个参数:state
:提供用来读取state
中的数据(状态)。value
:组件传递给 Vuex 要进行操作的数据。
在
mutations
中的方法名命名一般为大写与actions
中的方法进行区分。并且在mutations
中一般直接操作数据,而不再过多进行判断等业务逻辑(一般放在actions
中进行)。
在组件中使用 Vuex :
组件中读取 Vuex 中的数据:
$store.state.sum
(在模版中)组件中操作 Vuex 中的数据:通过
dispatch
:$store.dispatch('method', value)
或者commit
:$store.commit(‘method’, value)
。注意:若在该操作没有网络请求或者其他业务逻辑,组件可以越过
actions
的dispatch
,直接使用mutations
中commit
。
# getters
的使用
概念:当在
state
中的数据需要经过(复杂)加工之后使用,可以使用getters
加工。(在组件中的计算属性computed
只能提供在目前组件中使用,不能共享给其他组件复用)在
store
操作文件index.js
中追加配置getters
,注意要追加到暴露项中:
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
2
3
4
5
6
7
8
9
10
11
- 在组件中读取加工后的数据:
$store.getters.bigSum
。
# 四个 map
方法的使用
当初始化的数据较多,在组件中的模版语法使用过长的代码语句获取在 Vuex 的数据不太好,所以可以使用计算属性 computed
获取。
🌰 例子:使用计算属性获取 Vuex 中的数据:
点击查看
computed: {
sum() {
return this.$store.state.sum
},
school() {
return this.$store.state.school
},
subject() {
return this.$store.state.subject
},
bigSum() {
return this.$store.getters.bigSum
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看出在计算属性中有相同部分的代码,都是从 state
或者 getter
中获取数据,所以 Vuex 提供了生成 state
或者 getters
计算属性的方法 mapState
和 mapGetters
。
mapState
方法:可以映射state
的数据为计算属性:
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
2
3
4
5
6
7
mapGetters
方法:可以映射getters
的数据为计算属性:
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
2
3
4
5
6
7
🌰 使用原始方法与 Vuex 中的 actions
对话:
点击查看
methods: {
increment() {
this.$store.commit('ADD', this.n)
},
decrement() {
this.$store.commit('SUB', this.n)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.n)
},
incrementWait() {
this.$store.dispatch('addWait', this.n)
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
与上面两个 map
方法类似,Vuex 提供了帮助生成 actions
和 mutations
对话的方法 mapActions
和 mapMutations
。
mapActions
方法:用于帮助生成与actions
对话的方法,包含$store.dispatch(xxx)
的函数:
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd: 'addOdd', incrementWait: 'addWait'}),
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['addOdd', 'addWait'])
}
2
3
4
5
6
7
mapMutations
:用于帮助生成与mutations
对话的方法,包含$store.commit(xxx)
的函数:
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment: 'ADD', decrement: 'SUB'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['ADD','SUB'])
}
2
3
4
5
6
7
注意
使用数组法生成,要求事件名与在 actions
或者 mutations
中的方法名称相同,即在模板中要使用在 actions
或者 mutations
中的方法名称。
注意
此时 mapActions
与 mapMutations
帮我们生成的对象方法相当于:
increment(value) {
this.$store.commit('ADD', value)
}
2
3
说明要传递参数到 actions
或者 mutations
中的方法中,需要在模版中绑定事件时就传递好参数,否则参数是事件对象,例如: <button @click="increment(n)">+</button>
。
提示
真实依靠计算属性获取与使用 map
获取在 Vue 开发者工具中的区别:
# Vuex 的使用案例
# 🌰 使用 Vuex 实现求和案例
在 store
操作文件 index.js
中:
- 初始化数据,准备用于操作的数据
state
:
const state = {
sum: 0 // 当前求和的结果
}
2
3
- 准备用于响应组件中的动作
actions
:
const actions = {
add(context, value) {
context.commit('ADD', value)
},
sub(context, value) {
context.commit('SUB', value)
},
addOdd(context, value) {
if (context.state.sum % 2) {
context.commit('ADD', value)
}
},
addWait(context, value) {
setTimeout(() => {
context.commit('ADD', value)
}, 500)
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 准备操作(加工)数据(状态)的
mutations
:
const mutations = {
ADD(state, value) {
state.sum += value
},
SUB(state, value) {
state.sum -= value
}
}
2
3
4
5
6
7
8
在组件中使用 Vuex :
- 读取 Vuex 中的数据:
<h1>当前的求和为: {{$store.state.sum}}</h1>
- 操作 Vuex 中的数据:
methods: {
increment() {
this.$store.dispatch('add', this.n)
},
decrement() {
this.$store.dispatch('sub', this.n)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.n)
},
incrementWait() {
this.$store.dispatch('addWait', this.n)
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
注: n
为定义用来选择递增的数字。
提示
对上述改进,可以看出在 actions
中的 add
和 sub
方法并没有其他的业务逻辑,所以在组件中可以直接使用 commit
直接越过 actioins
:
methods: {
increment() {
this.$store.commit('ADD', this.n)
},
decrement() {
this.$store.commit('SUB', this.n)
}
}
2
3
4
5
6
7
8
# 🌰 多组件共享数据
除了第一个例子的 Count
组件以外,新加一个组件 Person
,使用 Vuex 共享两个组件都可以使用的数据。
详细步骤不赘述,其实不同组件获取 Vuex 中的数据都可可以使用普通方法或者 map
方法,从而实现共享数据。
# Vuex 的模块化编码
当多个组件同时使用 Vuex 实现共享数据并且操作数据时,此时的
store
文件其实「不堪重负」,在actions
和mutations
中的方法繁杂起来,不同组件操作不同的方法,造成编码混乱和版本管理困难。使用模块化编码的目的:让代码更好维护,让多种数据分类更加明确。
🌰 将上述的 Vuex 例子进行模块化编码,
修改 Vuex
store.js
,分割为如下文件:countOptions.js
personOptions.js
export default {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
2
3
4
5
6
7
注意:开启命名空间才能在组件中使用如
countOptions
的命名。
然后在 store.js
暴露时,使用 modules
包含以上的模块:
export default new Vuex.Store({
modules: {
countOptions,
personOptions
}
})
2
3
4
5
6
- 在组件中,使用模块化编码后:
读取 state
数据:
//方式一:自己直接读取
this.$store.state.personOptions.personlist
//方式二:借助mapState读取:
...mapState('countOptions',['sum','school','subject']),
2
3
4
5
读取 getters
数据:
//方式一:自己直接读取
this.$store.getters['personOptions/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countOptions',['bigSum'])
2
3
4
5
注意:
$store.getters
与state
不同。
调用 dispatch
:
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countOptions',{incrementOdd:'addOdd',incrementWait:'addWait'})
2
3
4
5
调用 commit
:
//方式一:自己直接commit
this.$store.commit('personOptions/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countOptions',{increment:'ADD',decrement:'SUB'}),
2
3
4
5