Vue 状态管理工具 — Vuex
1.简介
Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件状态,并以相应的规则保证状态以一种可预测的方式发生变化。可以理解为:将多个组件共享的变量全部存储在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,它最大的特点是响应式。
状态
— 实际上就是共享的数据,有时也称为状态数据,例如:A、B两个组件之间共用的某个对象n。
所以,一般情况下,我们会在 Vuex 中存放一些需要在多个界面中进行共享的信息。比如用户的登录状态、用户名称、头像、地理位置信息、商品的收藏、购物车中的物品等,这些状态信息,我们可以放在统一的地方,对它进行保存和管理。
2.搭建 Vuex 环境
2.1 安装
npm install --save vuex@版本号
这里注意,vue 的 2.x 版本对应 vuex 的 3.x 版本,vue 的 3.x 版本对应 vuex 的 4.x 版本
2.2 配置
配置 Vuex 的第一步是创建store
。
一般来说,实际开发中的标准方法是,在 src 中创建一个 store 文件夹,并在其中保存一个 index.js 来创建store
,然后再直接应用。
Vue2 创建方式
-
创建
store
:// index.js // 首先引入Vuex并使用插件 import Vue from 'vue import Vuex from 'vuex' //1.安装插件,不然会报错(不允许在使用插件之前创建store!) Vue.use(Vuex) //2.创建对象 const store = new Vuex.Store({ state:{ // ... }, mutations:{ // ... }, actions:{ // ... }, getters:{ // ... }, modules:{ // ... } }) //3.导出使用 export default store
-
应用
store
:// 在入口文件 main.js 中创建Vue实例时,配置 store 选项 import store from './store/index.js' ... new Vue({ el: '#app', render: h => h(App), store: store, })
完成这两步后,此时$store
属性就会出现在Vue实例对象以及组件实例对象身上了!
后续,要使用store
时,直接用this.$store
即可获取到!
Vue3 创建方式
与 Vue2 不同,在 Vue3 中不再使用Vuex.Store({...})
这一API,而是引入了新的方法createStore({...})
来创建store
。
同时,也不再需要在创建store
前应用插件了!
在创建store
之前,只需要在 main.js 中使用一下插件即可:app.use(vuex)
-
创建
store
:// index.js import { createStore } from 'vuex' export default createStore("xxx", { state:{ // ... }, mutations:{ // ... }, actions:{ // ... }, getters:{ // ... }, modules:{ // ... } })
-
使用
store
:在 Vue3 中,由于在 setup 函数中无法获取
this
指针,所以不再通过this.$store
来使用store
,而是在需要使用store
的模块中,引入 src/store/index.js 中暴露出来的useStore
方法!<!-- xxx.vue --> <template> ... </template> <script setup> import { useStore } from '@/store/index.js' ... const store = useStore(); ... </script>
即,通过一个变量接收
useStore()
方法返回的store实例,并通过这个实例来访问、修改store
。
3.Vuex的核心概念
vuex中一共有五个状态 State Getter Mutation Action Module
下面是官方给出的 Vuex 状态管理图例:
3.1 State
提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存
在vuex中state中定义的数据,可以在任何组件中进行调用!
需要注意的是,读取 Vuex 中的数据时,可以直接通过this.$store.state.xxx
或store.state.xxx
来获取。但是要修改其中的数据时,需要用dispatch
或commit
方法!
以 Vue2 中的使用为例:
this.$store.dispatch('actions中的某一方法名', 数据)
this.$store.commit('mutations中的某一方法名', 数据)
那么什么时候使用dispatch
方法,什么时候又要使用commit
方法呢?
实际上,从上面的使用方式中,就可以看出这两者的区别,即:dispatch
方法调用的是actions
中的方法,而commit
方法调用的则是mutation
中的方法!
那么这两者的区别又在哪?关于这一点后面会进行介绍,先往下看。
3.2 Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数/
定义 Mutation 中的方法时:
参数state
是必要的,同时也可以直接传递一个参数,例如:
mutations: {
add(state, num) {
state.count = state.count + num
},
}
而要使用 Mutation 中的方法,就需要用commit
方法触发:
this.$store.commit("add", 5); // Vue2
store.commit("add", 5); // Vue3
3.3 Action
Action 和 Mutation 相似,一般不用 Mutation 进行异步操作,若要进行异步操作,使用 Action
这里就可以回答上面那个问题,Action 与 Mutation 的区别在于:Action 中往往用于进行一些异步操作,例如定时器、向后端接口请求数据等等。Mutation 则往往用于执行一些同步操作。而这样设计是为了方便 devtools 跟踪数据变化,从而方便管理。
因此上面所说仅仅是规范,而不是逻辑上的不允许!
所以,对于上面的问题,即使用dispatch
和commit
方法的时机,可以总结为:若没有网络请求或其它异步业务逻辑,组件中也可以越过 Action,直接调用 Mutations 中的方法,即直接使用commit
方法!
而至于 Action 的使用,实际上与 Mutation 是相同的。
首先定义 Action 中的方法:
Action 中的方法接收一个必要参数context
,即上下文,实际上就是store
对象!同样的,其也可以接收一些自定义的参数。
actions: {
asyncAdd(context,num) {
setTimeOut(() => {
context.commit("add", num) // 通过Mutation中的方法改变state
}, 1000);
},
}
然后通过dispatch
方法触发:
this.$store.commit("asyncAdd", 5); // Vue2
store.commit("asyncAdd", 5); // Vue3
实际上,这里可以发现,在 Action 中处理异步操作时,其内部仅仅是处理异步逻辑,而真正改变state还是通过 Mutation 中的方法!
3.4 Getter
当 state 中的数据需要经过加工后再使用时,可以使用 Getter 进行加工!
Getter 实际上与计算属性 computed 十分相似,它们都是用于对数据进行加工后再使用,它们也同样会进行缓存!
例:
getters: {
powerCount(state) {
return state.counter * state.counter;
},
...
}
可以看到 Getter 中的方法,同样接收一个state
作为必要参数,但其不需要传入其他参数。
使用时,直接获取即可:
this.$store.getters.powerCount // Vue2
store.getters.powerCount // Vue3
但是如果真的需要传入参数,该怎么办呢?
此时可以在对应方法中,返回一个函数,例如:
moreAgeStu(state){
return function(age){
return state.students.filter(s => s.age > age)
}
}
这样,在调用时传入参数即可:
this.$store.getters.moreAgeStu(18) // Vue2
store.getters.moreAgeStu(18) // Vue3
4.Vuex的4个map方法 — 优化程序
4.1 mapState()
— 映射state中的数据为计算属性
借助mapState()
方法,我们可以根据state
生成计算属性,从state
中读取数据,实际上是由mapState()
方法生成了多个对应的函数返回state
中指定属性的值!
如何使用?
有两种方法:
-
对象写法:
import { mapState } from 'vuex' ... computed({ // 使用ES6的扩展运算符将生成的计算属性展开在computed中 // 对象写法可以指明生成的计算属性的名称 ...mapState({sum: 'sum', school: 'school', ...}) })
-
数组写法:
import { mapState } from 'vuex' ... computed({ // 仅指定需要读取的state中属性的名称,并将这个名称作为生成计算属性的名称! ...mapState(['sum', 'school', ...]) })
4.2 mapGetters()
— 映射getters中的数据为计算属性
借助mapGetters()
方法,我们可以根据getters
生成计算属性,从getters
中读取数据,实际上是由mapGetters()
方法生成了多个对应的函数返回getters
中指定属性的值!
同样有两种写法:
-
对象写法:
import { mapGetters } from 'vuex' ... computed({ // 使用ES6的扩展运算符将生成的计算属性展开在computed中 // 对象写法可以指明生成的计算属性的名称 ...mapGetters({bigSum: 'bigSum', ...}) })
-
数组写法:
import { mapState } from 'vuex' ... computed({ // 仅指定需要读取的getters中属性的名称,并将这个名称作为生成计算属性的名称! ...mapGetters(['bigSum', ...]) })
4.3 mapActions()
— 生成与actions对话的方法
借助mapActions()
方法,我们可以生成与 actions 对话的方法,即其中包含$store.dispatch('xxx',value)
的方法!
在 Vue2 中的使用如下:
import { mapActions } from 'vuex'
...
method: {
...mapActions(['ADD', 'SUB'])
}
4.4 mapMutations()
— 生成与mutations对话的方法
借助mapMutations()
方法,我们可以生成与 mutations对话的方法,即其中包含$store.commit('xxx',value)
的方法!
在 Vue2 中的使用如下:
import { mapMutations } from 'vuex'
...
method: {
...mapMutations(['ADD', 'SUB'])
}
4.5 在Vue3中的使用
由于 Vue3 中,不再使用data、method、computed等模块,所以导致上述4个方法在 Vue3 中的映射效果不如人意。那么,我们如何才能在 Vue3 中使用这些API呢?
答案是封装这4个方法!
详情参见:封装vuex的4个map方法
5.Vuex模块化 + 命名空间
当遇见大型项目时,数据量大,store
就会显得很臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
而为了模块拥有更高的封装度和复用性,我们还可以配合命名空间配置项namespace
一起使用。即,在模块中开启 namespace 配置:
xxxModule: {
namespace: true,
state: {
...
},
...
}
从而使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
所以,使用时需要指定模块名!