Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件状态,并以相应的规则保证状态以一种可预测的方式发生变化。可以理解为:将多个组件共享的变量全部存储在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,它最大的特点是响应式。
状态
— 实际上就是共享的数据,有时也称为状态数据,例如:A、B两个组件之间共用的某个对象n。
所以,一般情况下,我们会在 Vuex 中存放一些需要在多个界面中进行共享的信息。比如用户的登录状态、用户名称、头像、地理位置信息、商品的收藏、购物车中的物品等,这些状态信息,我们可以放在统一的地方,对它进行保存和管理。
npm install --save vuex@版本号
这里注意,vue 的 2.x 版本对应 vuex 的 3.x 版本,vue 的 3.x 版本对应 vuex 的 4.x 版本
配置 Vuex 的第一步是创建store
。
一般来说,实际开发中的标准方法是,在 src 中创建一个 store 文件夹,并在其中保存一个 index.js 来创建store
,然后再直接应用。
创建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
即可获取到!
与 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
方法!
<template>
...
template>
<script setup>
import { useStore } from '@/store/index.js'
...
const store = useStore();
...
script>
即,通过一个变量接收useStore()
方法返回的store实例,并通过这个实例来访问、修改store
。
vuex中一共有五个状态 State Getter Mutation Action Module
提供唯一的公共数据源,所有共享的数据统一放到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
中的方法!
那么这两者的区别又在哪?关于这一点后面会进行介绍,先往下看。
更改 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
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 中的方法!
当 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
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', ...])
})
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', ...])
})
mapActions()
— 生成与actions对话的方法借助mapActions()
方法,我们可以生成与 actions 对话的方法,即其中包含$store.dispatch('xxx',value)
的方法!
在 Vue2 中的使用如下:
import { mapActions } from 'vuex'
...
method: {
...mapActions(['ADD', 'SUB'])
}
mapMutations()
— 生成与mutations对话的方法借助mapMutations()
方法,我们可以生成与 mutations对话的方法,即其中包含$store.commit('xxx',value)
的方法!
在 Vue2 中的使用如下:
import { mapMutations } from 'vuex'
...
method: {
...mapMutations(['ADD', 'SUB'])
}
由于 Vue3 中,不再使用data、method、computed等模块,所以导致上述4个方法在 Vue3 中的映射效果不如人意。那么,我们如何才能在 Vue3 中使用这些API呢?
答案是封装这4个方法!
详情参见:封装vuex的4个map方法
当遇见大型项目时,数据量大,store
就会显得很臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
而为了模块拥有更高的封装度和复用性,我们还可以配合命名空间配置项namespace
一起使用。即,在模块中开启 namespace 配置:
xxxModule: {
namespace: true,
state: {
...
},
...
}
从而使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
所以,使用时需要指定模块名!