Vue – 数据共享插件 VueX 使用说明
简介
VueX 是专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读、写),也是一种组件间通信的方式,且适用于任意组件间的通信。
github:https://github.com/vuejs/vuex
工作原理
Vuex 的工作原理非常重要,在此如果不懂工作原理的,请耐心看完本节章。
先看官方原理图
Vuex 由三部分组成,分别是 Actions 、Mutations 、State。这三部分的基本职责如下:
State (状态) : 对象成员,用于储存用于共享的数据,所有数据包含在这个对象成员中。
Actions (激活) : 对 vue 组件提交(派发)数据更改任务时,进行前置加工处理,如果派发的任务不需要加工操作,可以省略这一步。
Mutations (变更) : 对接收到的派发任务进行处理工作,只有它才能对 State 内的数据进行更改。
store (仓库) : 作为管理 Actions 、Mutations 、State 三者运行的主要管理者,是 store 的属性成员
生动举例
把 Vuex 的工作原理比喻日常中的客户点餐作为举例:
情况一:客人对菜馆不熟悉时(当需要 Actions 进行前置处理数据时)
客人(Vue 组件)来到一家餐馆吃饭(Vuex),你找到服务员(Actions)过来,跟服务员下单说:“麻烦要一碟蛋炒饭(egg = egg+1)”,服务员记录下来之后,向后厨厨师(Mutations)下单说:“一碟蛋炒饭!(egg = egg +1)”。后厨厨师(Mutations)在厨房中生产出一碟蛋炒饭(State:{ egg:1 }),然后客人(Vue 组件)就能拿到一碟蛋炒饭(egg+1)了。
情况二:客人对菜馆熟悉时(当不需要 Actions 进行前置处理数据时)
客人(Vue 组件)来到一家餐馆吃饭(Vuex),径直的走到后厨,跟后厨老哥说:“老哥一份蛋炒饭”。后厨厨师(Mutations)认识你并知道你需要的口味(即不需要请求后端获得加工数据),直接在厨房中生产出一碟蛋炒饭(State:{ egg:1 }),然后客人(Vue 组件)就能拿到一碟蛋炒饭(egg+1)了。
实际原理
情况一:需要进行前置时,在 Actions 中进行前置处理:
仓库中有一个数据 foo,它的值为 0
<component派发> -> dispatch('setFoo') ->
<Actions前置处理> -> commit('setFoo', props) ->
<Mutations操作> -> state.foo = props
1.组件要求修改位于 State 中的 foo 的数值,于是向 Actions 中派发修改任务:
store.dispatch('setFoo')
2.Actions 收到派发任务,但不知道 foo 的值需要改成多少,于是向服务器请求修改值,得知需要改进步进值为 step,并再次提交给 Mutations 进行修改值处理:
const step = await axios.get('...')
this.commit('setFoo',step)
3.Mutations 收到修改值任务后,对 foo 的值进行处理:
setFoo(state, step){
state.foo += step
}
4.最后,foo 的值得到了修改。
情况二:不需要前置处理,由组件提供处理详细数值:
仓库中有一个数据 foo,它的值为 0
<component派发> -> commit('setFoo', props) ->
<Mutations操作> -> state.foo = props
1.组件要求修改位于 State 中的 foo 的数值,因为组件有明确表示需要更改数值是多少,所以可以省过 Actions 步骤,直接调用 Mutations 操作:
this.commit('setFoo',2)
2.Mutations 收到修改值任务后,对 foo 的值进行处理:
setFoo(state, 2 ){ state.foo += 2 }
3.最后,foo 的值得到了修改。
基础配置
1.安装 Vuex
注意:在2022年2月7日,vue3成为默认版本,直接使用 npm i vuex
安装的,直接就是 Vue3版本的 Vuex 了,且Vuex 也升级到 4.x 版本.在 Vue2 中安装 Vuex 4 版本会出现如下报错:
所以,Vue2.x 版本只能使用 Vuex 3.x 版本,Vue3.x 版本只能使用 Vuex 4.x 版本。
因此在 Vue2 项目中,请执行 npm i vuex@3
安装
2.引入使用 Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3.创建 Vuex 仓库 store
// 引入插件
import Vue from 'vue'
import Vuex from 'vuex'
// 使用插件,执行 Vuex.Store 之前,一定要先执行 Vue.use(Vuex)
Vue.use(Vuex)
// 负责存储数据
const state = {}
// 负责响应任务
const actions = {}
// 负责操作数据
const mutations = {}
const store = Vuex.Store({
state: state,
actions: actions,
mutations: mutations
})
export default store
注意:import 操作不管在任何位置,都会被优先执行
4.在 vm 中加入仓库
new Vue({
...,
// 挂载 store 仓库
store
}).$mount()
为什么要在 Vue 实例中加入 store?
在 Vue 实例中,如果我们强加自定义成员变量,在vm 是不会被识别的:
const app = new Vue({
...App,
name:'tzming', // 在 vm 中是不会存在 name 和 age 的,即使在这里定义了也不会存在
age:18
})
但是当你使用 Vue.use(Vuex) 时,Vuex 作为一个插件,在 Vue 实例中创建了一个 $store 的成员,在 Vue 实例中则为 store 成员
const app = new Vue({
...App,
store: ???? // 当 Vue.use(Vuex) 时,Vue 实例中便存在 store 成员
})
虽然使用 Vue.use(Vuex)
后,Vuex 插件在 Vue 实例中创建一个 store
成员,但是这个成员现在还没有被赋值,而此成员的需要一个符合 Vuex 规范的的数据类型。
于是,使用 Vuex.Store()
创建一个符合 store
成员规范的数据(也可以定义为可用于存储操作的仓库),并把创建出来的数据赋给 Vue 实例中的 store
成员。
// 负责存储数据
const state = {}
// 负责响应任务
const actions = {}
// 负责操作数据
const mutations = {}
const store = Vuex.Store({
state: state,
actions: actions,
mutations: mutations
})
new Vue({
...App,
store: store // 把上面创建的符合规范的 store 类型数据赋给 Vue 实例中的 store 成员 >> 名字相同,可以简写为 store
})
虽然 Vue.use(Vuex)
使用了 Vuex 的插件,但也只使用了 Vuex 的插件功能,但并没有告诉 Vue 实例,要操作的仓库在那里,所以store
则是这个要操作的仓库了。
使用说明
state 定义数据
在 State 对象中定义数据:
const state = {
foo:0
}
dispatch() 派发任务
1.在组件中调用 this.$store.dispatch()
即可调用派发任务,该任务会在 actions
中执行
this.$store.dispatch('actionsFoo',prop)
const actions = {
actionFoo(context,value){
// 调用 dispatch 会被调用 actions 中的对应名称方法
}
}
2.actions
中返回两个参数,
参数1:context
(上下文) : 非 store
本身,但把一些可能用上的方法封装成一个对象提供调用
context = {
commit : 函数,用于提交任务给 mutations
dispatch : 函数,用于提交任务给 actions
getters : 对象,Vuex中的 getters 计算属性
rootGetters :
rootState:
state : 即是 store 中的 State 数组对象
}
注意:建议不要在actions
中的 context.state
中修改数据,因为 Vue tools 数据是监视mutations
来的,如果直接在 actions
中修改state
中的数据,会影响 Vue tools 中不能调试数据。
参数2:value
(值) : 即组件在 dispatch 中传的第二个参数值。
3.调用 context.commit('FOO',2)
会被执行mutations
中的FOO
方法
const actions = {
actionFoo(context,value){
context.commit('FOO',value)
}
}
context 中为什么存在 dispatch 等方法?
Vuex 作者考虑到,有一些数据可以在前置处理时,算法较多,且复杂,如果前置处理只能在一个 Actions 方法中实现,那有可能对一些需要复杂前置处理的方法代码比较多且不利于阅读,所以 Actions 支持在 Actions 方法中调用 Actions 方法,让算法多的前置处理分开多个 Actions 方法执行,利于逻辑和阅读。
commit() 提交任务
1.当actions
或 组件 调用commit
方法时,即会执行mutations
中的方法
mutations: {
FOO(state, value) {
// 在actions 中 调用 commit('FOO',value) 会执行此处
},
},
2.mutations 中返回两个参数
参数一: state :即是 store 中的 State
,可直接使用 State
中的成员数据进行修改
参数二: value:即是由commit
接收过来的 value
3.在 mutations
中执行数据修改工作
mutations: {
FOO(state, value) {
state.foo = value
},
},
直接调用 commit() 跳过 actions
当修改数据中不需要actions
中做前置数据处理时,可以在组件中直接调用commit
方法直达mutations
this.$store.commit('FOO',2)
getters 计算属性
getters 是 Vuex 实例中用于加工 State
里的数据的一个成员对象,它的作用是对 State
中的数据进行加工后返回,和 Vue 中的computed
计算属性功能相似。
// 定义 getters 的对象成员
const getters = {
foo(state){
return state...
}
}
// 在 Vuex 中加入 getters 成员数据
const store = Vuex.Store({
...
getters:getters
})
getters 中的计算函数包含一个参数
参数一state
: 指的即是 State 中的成员数据。
为什么要选择使用 getters 而不在组件中使用 computed
因为 Vuex 的数据在State
区域,当出现计算加工比较复杂时,建议使用getters
来进行计算,这样计算属性里的数据也能共享使用。
当然可以在组件别调出 中的数据,要使用组件中的 computed
计算属性进行计算后输出,但是 computed
计算属性只允许在当前组件中使用,那万一这个计算属性的值需要在多个组件中使用呢,若使用computed
的方法的话,那所有需要使用到这个计算属性的值时都要创建 computed
来重新编写计算算法,代码冗余高。
而使用getters
刚可以在store
中一次编写计算属性,多个组件共享使用,组件要使用这个计算属性不需要再定义computed
了。
mapState
上面通过详细说明,我们可以知道,组件如何通过一些操作对 Vuex 里的数据进行赋值修改,但是从这里开始,需要探讨组件如何读取 Vuex 中的数据.
基础读取
我们可知道,store成员在 Vue 实例中,以 $store
成员存在,而 $store
中就包含了 Vuex 实例中的 State
成员,其中 $store.state
存储的实则为 Vuex 实例中的 State
中的数据,那么我们可以通过直接取值的方式取得 State
中的值
const state = {
prop1:'p1',
prop2:'p2',
prop3:'p3',
}
{{ $store.state.prop1 }}
{{ $store.state.prop2 }}
{{ $store.state.prop2 }}
....
但是我们从上面的语法中看出,每一次调取数据,我们都要重复的编写 $store.state
这样的变量路径,这样对代码的阅读和开发都非常不友好。
我们可以通过组件的计算属性computed
来简化变量名
computed: {
prop1() {
return $store.state.prop1
},
prop2() {
return $store.state.prop2
},
prop3() {
return $store.state.prop3
},
chanshu4() {
return $store.state.prop4
},
},
其中使用 computed
可以自定义变量名(chanshu4 即是)。
但是问题又来了,如果重复定义 computed
计算属性,除了增加非常多的工作量外,还会影响代码阅读性。
是否存在一种功能,只要我们提供计算属性和state
的变量名,就能自动生成对应的计算属性函数?答案是有的,那就是 mapState !
引入 mapState
import { mapState } from 'vuex'
对象方法
Vuex 向我们提供一个代码生成函数mapState
, 只要我们提供计算属性名和要取的state
的成员名,它能自动生成计算属性函数。
mapState 支持对象定义方法:
mapState({
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
chanshu4: 'prop4'
})
在 mapState
中传入一个对象,对象的key
为计算属性名,对象的value
为State
中的成员名。这样,mapState
就会自动生成上面 基础读取 那样的代码:
{ // mapState 返回一个对象,成员如下
prop1:function(),
prop2:function(),
prop3:function(),
chanshu4:function(),
}
注意:
1.mapState
返回的是计算属性的函数,所以必须在 computed
属性内定义
2. mapState
返回的是由计算属性函数组成的对象,直接放在 computed
属性中会出现错误
3. 使用 ES6 解构方法“ ... ”把 mapState
返回的数据解构到 computed
属性
computed: {
...mapState({
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
chanshu4: 'prop4'
})
},
数组方法
一般来说,我们的计算属性名和State
成员名都是一样的,那么我们可以使用数组的方法定义,无需使用对象方法定义。
computed: {
...mapState(['prop1','prop2','prop3','prop4',])
},
这样相当于下面的对象方法
computed: {
...mapState({
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
prop4: 'prop4'
})
},
开启命名空间
Vuex 支持开启命名空间
export default {
// 开启命名空间
namespaced: true,
state:{},
actions:{},
mutations:{},
}
开启命名空间后,在 module 模项模式中,mapState
方法可以在项目中加入命名空间来调用对应模块的 State
// 导入模块化的 Vuex 仓库
import foo from '@/store/modules/foo'
// 在创建 Vuex 实例时使用 modules 模块式
export default new Vuex.Store({
modules: {
// 定义模块名为 mFoo 使用的模据是 foo
mFoo: foo
}
})
mapState
中使用命名空间调用对应模块的 State
数据
...mapState('mFoo',{fooProp:'foo'})
或
...mapState('mFoo',['foo'])
mapGetter
mapGetter
的使用方法和 mapState
方法一致,但主要取getters
中的数据
引入 mapGetter
import { mapGetter } from 'vuex'
对象方法
computed: {
...mapGetter({
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
chanshu4: 'prop4'
})
},
数组方法
computed: {
...mapGetter(['prop1','prop2','prop3','prop4',])
},
开启命名空间
Vuex 支持开启命名空间
export default {
// 开启命名空间
namespaced: true,
state:{},
actions:{},
mutations:{},
}
开启命名空间后,在 module 模项模式中,mapGetter
方法可以在项目中加入命名空间来调用对应模块的 getters
// 导入模块化的 Vuex 仓库
import foo from '@/store/modules/foo'
// 在创建 Vuex 实例时使用 modules 模块式
export default new Vuex.Store({
modules: {
// 定义模块名为 mFoo 使用的模据是 foo
mFoo: foo
}
})
mapGetter
中使用命名空间调用对应模块的 getters
数据
...mapGetter('mFoo',{fooProp:'foo'})
或
...mapGetter('mFoo',['foo'])
mapMutation
除了使用 $store.state
来获得 Vuex 实例中的共享数据,而当我们需要修改共享数据时,我们需要使用 $store.dispatch()
或 $store.commit()
方法进行派发数据修改任务。
通过在 method:{}
对象成员中创建一个方法,来执行派发任务操作:
methods: {
commitFoo(){ // 派发到 Mutations 里
this.$store.commit('COMMITFOO',foo)
},
}
而如果我们对每一个数据的修改,都要创建一个method
,对程序员和程序来说也同样非常不友好的。
所以 Vuex 提供了一个能自动生成派发任务函数的方法mapMutation
,它能自动在methods
对象方法中生成包含所有派发任务方法的对象
对象方法
methods: {
...mapMutation({ commitFoo1:'COMMITFOO', commitFoo2:'COMMITFOO2' })
}
当需要调用的方法名和 Mutations 中的方法名不一致时,可以使用对象方法进行定义,key 为 methods 中的方法名,value 为 Mutations 中定义的方法
数组方法
如果methods
中的方法名和Mutations
中的方法名允许相同,那么可以使用数组的方式进行定义:
methods: {
...mapMutation([ 'COMMITFOO', 'COMMITFOO2' ])
}
当然,在大多数情况下,为了方便程序阅读性,我们都不采用 methods 和 Mutations 相同函数名的方案,所以更多使用对象方法定义。
传值方法
如果是程序员手动编写派发任务的话,我们可以在派发任务时附带参数传递:
this.$store.commit('COMMITFOO',prop)
但是使用 mapMutation 生成派发任务时却没有传递参数的操作:
...mapMutation({ commitFoo1:'COMMITFOO' })
当我们调用方法时,如果我们不进行传参的话,methods
方法收到的第一个参数则为这个方法的Event
事件对象
<button @click="click"></button>
methods: {
click( event ){
// 不传参的情况下,默认接收 Event 事件对象
}
}
而mapMutation
所生成的方法中,如果调用方法不带参数的话,Mutations
方法中收到的value
将会是event
对象方法
<button @click="click"></button>
methods: {
...mapMutation({ click:'CLICK' })
}
mutations: {
CLICK( state, value ){
// 这里收到的 value 则会被接收到 event 数据
}
}
因此,如果我们要使用 mapMutation
进行生成代码时,调用方法必须加上参数
<button @click="click( foo )"></button>
methods: {
...mapMutation({ click:'CLICK' })
}
mutations: {
CLICK( state, value ){
// 这里收到的 value 则是 foo
}
}
开启命名空间
Vuex 支持开启命名空间
export default {
// 开启命名空间
namespaced: true,
state:{},
actions:{},
mutations:{},
}
开启命名空间后,在 module 模项模式中,mapMutation
方法可以在项目中加入命名空间来调用对应模块的 mutations
// 导入模块化的 Vuex 仓库
import foo from '@/store/modules/foo'
// 在创建 Vuex 实例时使用 modules 模块式
export default new Vuex.Store({
modules: {
// 定义模块名为 mFoo 使用的模据是 foo
mFoo: foo
}
})
mapMutation
中使用命名空间调用对应模块的 S
数据
...mapMutation('mFoo',{fooMethod:'foo'})
或
...mapMutation('mFoo',['foo'])
为什么 Actions 中的方法名使用小写字母定义,而 Mutations 中的方法名使用大写字母定义?
因为 Actions
是前置处理对象,大多数在处理完后都会移交下一任进行数据修改操作,而通常同一派发任务,Actions
中的方法和 Mutations
中的方法往往是相同的,这样就容易告成方法名混乱,程序员无法精确分辩出那个方法是 Actions
的那个方法是 Mutations
的。
所以我们建议,Actions
中的方法使用小写定义,而 Mutations
中的方法使用大写定义,这样就能清楚分辩,那个方法是属于那个区域的派发任务方法了。
Vuex 模块化
Vuex 提供了模块化的功能,以往我们使用 Vuex 进行数据仓库管理的时候,我们把actions
、mutations
、state
三个放在一个 store/index.js
文件上定义,然后多个组件对这三个成员进行操作而达到数据共享的目的。
// 负责存储数据
const state = {}
// 负责响应任务
const actions = {}
// 负责操作数据
const mutations = {}
const store = Vuex.Store({
state: state,
actions: actions,
mutations: mutations
})
export default store
但是当存在一个情况,即如果多个组件都有属于它们需要共享的数据和 actions
或 mutations
时,所有组件的数据都往这个 index.js 文件中存放的话,会使文件非常臃肿,而且各组件的数据混乱,能不能有一个办法,可以创建多个 js 文件,存放各自组件自己需要共享的数据呢,答案是使用 模块化。
模块化实现
要实现模块化,那么action
、mutations
、state
、getters
这些就不止一份了,而是有多份了,就不能使用上面那种方法把四个成员加载到 Vuex.Store()
中去了
先把模块以一个对象包含起来,再使用 modules:{} 成员定义各模块
// 先定义 store 所需要的成员,可以把不同的 store 模块分离到别的 js 文件中去
const fooCom = {
const state = {}
const actions = {}
const mutations = {}
const getters = {}
}
// 使用 modules 载入 store 成员模块,key 可以自定义名称,但该名称会被使用来识别那个模块的模块名
const store = Vuex.Store({
modules:{ m_foo : fooCom }
})
export default store
至此,即完成了 Vuex 的模块化操作
调用模块
模块化后的store
,不能使用 mapState(['foo'])
这样的定义了,因为 mapState(['foo'])
没有定义foo
是那个模块的,所以必须要告知 mapState(['foo'])
中的foo
具体位置
...mapMutations('moduleName', ['foo']),
...mapActions('moduleName', ['foo']),
...mapState('moduleName', ['foo']),
...mapGetterns('moduleName', ['foo']),
使用模块化后,如果要调用生成方法,要在前面加一个参数,就是告知数据的所在模块。
命名空间
通过了调用模块的 map 方法更改后,此时, Vuex 还是会报出一个错误,说依然找不到模块,这是因为,虽然我们定义了模块名,但是 Vuex 不会使用,因为 Vuex 默认有一个参数 namespaced
:false
。要使模块名能有效,我们可以设置 namespaced
为true
const fooCom = {
namespaced : true,
const state = {}
const actions = {}
const mutations = {}
const getters = {}
}
这样,map 系列的代码生成器就能正常使用了。
不使用命名空间访问 Vuex
假如我们不设置namespaced
为true
时,我们如何访问 Vuex 中的成员呢?
我们知道,当不使用模块化的 Vuex 时,我们可以通过 $store.state
中获得数据,但是当模块化后,我们的state
等数据会藏在module
之后,我们可以使用
$store.state.modules
获得数据。同理,我们需要操作actions
或mutations
也需要增加模块名,但和 map 系列不一样,使用 $store.dispatch('moduleName/foo', value)
方法来实现派发任务。
$store.state.moduleName.foo // 取得 state 数据
$store.dispatch('moduleName/foo', value) // 派发任务给 Actions
$store.commit('moduleName/foo', value) // 派发任务给 Mutations
$store.getters['moduleName/foo'] // 取得 getters 数据,因为 getters 的成员名是 "module/foo",不能使用 . 语法,使用ES6支持的 [''] 语法]
共有 0 条评论