目录
一、Vuex用法指南
1. Vue和Vuex的关系
Vuex是专为vue设计的状态管理框架,类似(并且借鉴自)React的Flux。
我们知道,Vue的设计借鉴了MVVM(Model-View-ViewModel)的思想,也就是由业务数据驱动视图渲染。它使用template来定义视图模板,用data来管理业务数据。通过将data中的数据绑定到模板,并对data的值进行监听,来自动渲染和更新视图。比如下面的组件:
ArticleTitle.vue
<template>
<h1>{{title}}</h1>
</template>
<script>
data () {
return {
title: 'Vuex指南'
}
}
</script>
这个组件所使用的模板由template
定义,模板内通过{{ title }}
插值语法,将业务数据data
中的变量title
绑定进来。首绘时页面会渲染title的初始值:'Vuex指南'
,以后任何时候只要修改title的值,视图都会依据新的值重新渲染。
业务数据可以跨组件传递,但是只能从上级组件向下级组件传递,不能逆向或跨兄弟组件(泛指没有嵌套关系的组件)传递。如果子组件需要修改父组件中的数据,需要向父组件触发update事件来更新,不允许直接修改。
那么问题就来了:假如有一组业务数据,它在多个组件的视图中会用到,并且这些组件没有直接的嵌套关系,那么该在哪保存和管理这个数据,才能保持所有组件的引用是同步的呢?
Vue给出的官方解决方案是,使用Vuex将这组数据作为公共数据提取出来,单独维护。所有组件都直接与这组公共数据交互,从而保证组件状态的同步。
为了对这组公共数据进行规范管理,Vuex定义了五个重要概念:state、getters、mutations、actions和modules。
state
的含义是“状态”。在计算机学科中,用于描述某个实体运行规则的数学模型被称为“状态机”,状态机的某个特定的状态被称为state。在这里,对共享数据的管理逻辑可以抽象为一个状态机,state描述的就是它管理的这组共享变量,从state中你可以知道被管理的变量在当前状态下的值。getters
是Vuex版本的计算属性。mutations
是Vuex提供的修改state中变量值的唯一方法。actions
是封装数据处理逻辑的一般方法,常用来封装异步操作。modules
是Vuex的模块拆分方案,用于解决由变量过多导致的难以维护的问题。
Vuex最简单的用法如下:
main.js
import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';
Vue.use(Vuex);
const store = Vuex.store({
state: { name: 'carter' },
mutations: {
setName: (state, name) => {
state.name = name;
}
}
})
new Vue({
store,
render: h => h(App)
}).$mount('#app');
现在在整个vue应用的任何一个组件内,我们都可以通过this.$store
访问到这个全局store,包括从state中取值,或通过mutations提交修改等(这里的逻辑比较简单,因此我们没有定义getters、actions和modules等属性)。如:
App.vue
<template>
<h1>{{name}}</h1>
</template>
<script>
data(){return {};},
computed: {
name: this.$store.state.name;
}
</script>
通过当前组件对象this
上的$store
属性,即可访问到全局store,它提供了state、getters、mutations、actions等属性。
下面我们分别详细介绍这五个概念。
2. 核心概念介绍
(1). state
如果与Vue的概念进行类比,Vuex中的state可以理解为Vue的data,即业务数据。但是Vuex的定位是状态管理,它不负责任何View(视图层)和ViewModel(绑定层)的逻辑,仅仅用于维护共享变量的状态,因此它沿用了状态机中的概念,用state来访问变量。
由于Vuex是依赖Vue的状态管理框架,因此它的状态也是响应式的。等价的,它与Vue有着同样的约束,如不能直接通过索引修改数组某项的值,为对象新增的属性不是响应式的等。相应的解决方案与Vue也是一致的,这里不再赘述。
关于state,最重要的是要记住它维护的变量值永远只能通过mutations
来修改,这是为了保证数据变化的可追踪(关于Vuex如何追踪数据变化,后面的原理部分会结合部分源码介绍),提高数据的可维护性。
在组件中引用state,可以通过this.$store.state
,一般我们会将其映射到一个计算属性,如:
...
computed: {
name() {
return this.$store.state.name;
}
}
...
也可以不映射到计算属性,而是在组件的任意地方直接访问这个值(在模板中使用时不需要this
),如:
<template>
<h1>{{ $store.state.name }}</h1>
</template>
...
methods: {
getName(){
return this.$store.state.name;
}
}
如果需要将state的多个值分别映射到组件的计算属性,一个个像上面那样写未免过于麻烦。为此Vuex提供了一个辅助函数mapState
,可以帮助你少写一些代码:
import { mapState } from 'vuex';
export default {
// ...
computed: mapState({
count: state => state.count,
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
注意,只有使用常规函数,才能访问当前组件对象this
,箭头函数内部是没有定义this
的。如果不需要为变量改名,mapState也可以接受一个字符串数组:
...
computed: mapState(['count', 'name']),
...
这样你就把state中的变量count
和name
映射成了当前组件的同名计算属性count
和name
。于是你可以在模板中这样方便地绑定它的值:
<template>
<h1>{{ count }}</h1>
</template>
使用es6的展开符可以组合state中的变量和常规计算属性:
computed: {
...mapState(['count', 'name']),
menuLength() {
return this.menus.length;
}
}
由于组件中不允许直接修改state中变量的值,只能用于取值,所以它和计算属性一样,是“只读”(狭义上的只读,指不允许直接进行写操作)的。又因为state和计算属性一样都是响应式的,因此我们才常把state中的变量映射到组件的计算属性。
不过state中的变量并不是真正意义上的“只读”,它可以通过提交mutation来修改,后面介绍mutation时会介绍。
(2). getters
getters之于Vuex,就如computed之于Vue,用于定义较为复杂的取值逻辑。
现在假设我们在state中有一个菜单数组menus
,它的每一项带一个root
属性,标识当前菜单是否为根节点:
state: {
menus: [
{ id: '1', ..., root: true }
{ id: '2', ..., root: true }
{ id: '11', ..., root: false }
...
]
}
假如我们需要在很多个组件中用到由根节点组成的菜单数组,常规的思路是在组件中定义一个计算属性,或者定义一个函数来过滤根节点,如:
computed: {
rootMenu() {
return this.$store.state.menus.filter((menu) => {
return menu.root;
})
}
}
// 或
methods: {
getRootMenu(){
return this.$store.state.menus.filter((menu) => {
return menu.root;
})
}
}
由于计算属性可以基于依赖对结果进行缓存,因此第一种实现的性能高于定义函数。但如果有许多组件都依赖这个值,并且实际的取值复杂度原高于这里呢?你需要在每个组件都重写这个复杂的取值逻辑!
或者你为了方便,直接把根菜单数组保存在store里(假设是rootMenu)。但这是不可取的,因为假如menus
发生变化,rootMenu
的值就可能受影响,所以你必须在维护menus
的值时,同时去维护rootMenu
,这就显著增加了mutations逻辑的复杂性,以及系统出bug的概率。
Vue中为了解决这个问题,提出了computed,对计算结果进行缓存,Vuex则提出了getters。
我们可以用getters这样定义rootMenu:
state: { menus: [...] },
getters: {
rootMenu(state) {
return state.menus.filter((menu) => {
return menu.root;
})
}
}
在组件中使用getters很简单:
computed: {
rootMenu() {
return this.$store.getters.rootMenu;
}
}
或者像state一样,用辅助函数mapGetters
快速映射到组件的计算属性:
import { mapGetters } from 'vuex';
// ...
computed: {
...mapGetters(['rootMenu'])
}
现在在组件中,我们直接使用getters中的rootMenu即可,不需要重新封装处理逻辑。最重要的是,它和computed一样,可以对计算结果缓存,并且会在所依赖的变量变化时自动更新。
有人可能会好奇,为什么getters中的命名使用的是名词,而不是诸如getRootMenu
这样的命名法呢?
其实这和Vue的computed是一个道理。在Vuex中,getters是作为state变量的计算属性存在的,也就是说,getters的每一项被Vuex视为一个变量(从表面上看它虽然是函数,但那只是为了封装计算过程,它在逻辑上被视为变量),只是它的值依赖于state中其他变量的值。
getters可以接受store的getters属性作为第二个参数,通过它可以访问其他getters,这样可以组合出更加复杂的取值逻辑:
getters: {
rootMenuLength(state, getters){
return getters.rootMenu.length;
},
rootMenu(state) {
return state.menus.filter((menu) => {
return menu.root;
})
}
}
另外,getters也是可以接受参数的,不过此时的getters必须返回一个函数。比如我们需要传入菜单的level过滤出某个层级的菜单节点:
getters: {
getMenuByLevel(state) {
return function(level) {
return state.filter(menu => {
return menu.level === level;
})
}
}
// 或者这样写
getMenuByLevel: (state) => (level) => {
return state.filter(menu => {
return menu.level === level;
})
}
}
可以看到,这时getters不再用名词命名,因为从功能逻辑上它已经变成一个根据level查询结果的函数,此时它应该这样调用:
...
let secondMenuList = this.$store.getters.getMenuByLevel(2);
...
并不是从state中取值总要定义getters,getters仅用于封装复杂的取值逻辑。
(3). mutations
用于向store提交一个修改事件,这是Vuex提供的修改state变量值的唯一方法。
注意,并不是说下面的语法无法修改state的值:
this.$store.state.name = '张三';
这个语句从正确性来说没有任何问题,它会正确修改state.name
的值,并且会正常触发响应式系统。
它的唯一问题是,当有多个组件依赖这个变量的值时,没人知道是哪个组件修改了它的值,也不知道是何时修改的,这个变量的修改过程无法被追踪。假如当前系统非常庞大,数据依赖复杂,这种无法追踪的修改会导致代码难以调试和维护。
为此,Vuex规定,state中变量的值必须通过事件来修改,这样Vuex就可以通过注册钩子函数来追踪状态变化,这也是各大状态管理框架的常规思路。
你可以像下面这样定义一个更新state的逻辑:
state: { name: 'carter' },
mutations: {
setName(state, name) {
state.name = name;
}
}
这里定义了一个修改事件:setName
,Vuex会在调用它的时候自动传入当前state
对象,通过它你可以完成对state的修改。在组件中,如果你想修改state中的值,可以通过提交这个事件,并传入参数:
this.$store.commit('setName', '张三');
// 或者
this.$store.commit({type: 'setName', name: '张三'});
Vuex在Store的原型上提供了一个可以订阅修改事件的方法:subscribe
,如果你需要编写一个在state的值发生修改时进行处理的Vuex插件,一定会用到这个方法(Vuex自身提供的状态追踪,本质上就是通过这个方法注册监听mutation事件的钩子函数实现的)。
通过mapMutations
辅助函数,你可以把store中的mutations
映射成组件的方法:
import { mapMutations } from 'vuex';
...
methods: {
...mapMutations(['setName']),
localFn (name) {
this.setName(name);
}
}
mapMutations
的操作实际上非常简单,它只是将setName
处理成以下实现:
methods: {
setName(name) {
this.$store.commit('setName', name);
}
}
mutations
有一个严格的规定,就是禁止包含异步操作,这是为了让Vuex能正确追踪state的变化。
Vuex追踪state变化的逻辑是,向所有的mutations
事件注册一个钩子函数,一旦某个mutation
函数(如setName
)执行完毕,Vuex会立即查询当前state的状态,并显示在devtools中。注意,查询state状态的回调函数是提前注册好的,它不会等待mutation
中的异步逻辑执行完才调用,也就是说,Vuex在state还未发生变化时就去查询了当前state的状态,而state真正变化时,Vuex并不知道,这样Vuex就无法实现对state的追踪了。
注意,在mutations
中包含异步逻辑并不会导致系统异常,它只是会让Vuex的追踪功能(以及其他与state相关的功能)失效。
那么遇到需要异步处理的逻辑该怎么办呢?答案就是提交actions
。
(4). actions
actions
是为了解决mutations
不能包含异步代码而设计的。我们可以这样理解actions
:它封装了一些异步操作,这些异步操作的结果将用于修改state的值,当它得到这些值时,会通过提交mutations
来修改state的值。
言外之意,actions
不修改state的值,它对state造成的影响是通过提交mutations
体现的。下面是一个简单的actions:
import axios from 'axios';
...
state: { name: 'carter' },
mutations: {
setName(state, name) {
state.name = name;
}
},
actions: {
fetchName(store, url) {
axions.get(url).then(res => {
store.commit('setName', res.data);
})
}
}
在组件中这样分发一个action:
...
this.$store.dispatch('fetchName', url);
...
这个语句会触发名为fetchName
的action
,并传入参数url。Vuex会调用对应的函数,传入当前的store对象,并将url作为第二个参数传入。然后在函数内,axios会发送一个请求,得到用户名后通过store.commit('setName', res.data)
提交修改name的事件,state.name
随即发生变化。
如果你不需要完整的store对象,可以用es6的解构赋值提取需要的方法:
fetchName({ commit }, url) {
axions.get(url).then(res => {
commit('setName', res.data);
})
}
store的所有属性都可以这样提取出来,以方便使用。
actions
可以像mutations
一样映射为组件方法,这需要使用mapActions
辅助函数:
import { mapActions } from 'vuex';
...
methods: {
...mapActions(['fetchName']);
}
// 调用格式为:this.fetchName(url);
在实际使用actions
时,我们经常需要知道actions
何时执行完毕,以为其定义后续的处理逻辑,或者在某些情况下组合使用多个action
,这可以借助Promise或者async函数实现:
actions: {
fetchName(store, url) {
return new Promise(function(resolve, reject) {
axions.get(url).then(res => {
store.commit('setName', res.data);
resolve();
}).catch(e => {
reject(e);
})
})
}
// async的版本
async fetchName(store, url) {
store.commit('setName', await axios.get(url));
}
}
现在这个actions
函数返回的是一个Promise对象,因此你可以为它注册后续的回调事件:
this.$store.dispatch('fetchName', url).then(res => {
// state.name变化后的操作
})
你还可以组合使用action
,来完成更复杂的逻辑,比如我们现在要提交一个actionB
,但它需要另一个actionA
执行完后才能执行,于是可以定义下面的action
:
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
现在当你在组件中分发一个actionB
时,它在函数内会自动分发actionA
,等待其执行成功,才会继续执行后面的逻辑。
Vuex在Store的原型上定义了subscribeAction
方法,来订阅特定的action
事件,并为其定义钩子函数。如果你需要编写处理action
的Vuex插件,很可能需要这个方法。
(5). modules
这是为了对庞大的store进行模块化分解而设计的。
假如某个应用极其复杂,有上百个共享变量需要在state维护,与每个共享变量相关的getters、mutations、actions更是数不胜数,那么这个store的庞大程度可想而知。另外,假如store中的state由不同的开发人员维护,还可能发生命名冲突、意外修改等严重的问题。
为此,Vuex提出将state的变量按照一定逻辑划分成不同的模块,单独维护。每个划分出来的模块,就称为一个module
。它包含自己专有的state、getters、mutations、actions,甚至可以继续定义自己的子模块modules:
moduleA
export const moduleA = {
namespaced: true, // 使用单独的命名空间
state: {},
mutations: {},
actions: {}
}
index.js
import moduleA from './module/moduleA';
new Vuex.Store({
state: {},
mutations: {},
actions: {}.
modules: {
moduleA
}
})
module中有一个特殊的属性,叫namespaced
,表示是否要使用单独的命名空间。默认情况下,这个值为false
,即不启用单独的命名空间。
此时所有模块内部的getters、mutations和actions都是注册到全局命名空间的,也就是说向全局store提交的commit,或者派发的action也会被子模块监听到。如:
new Vuex.Store({
state: {},
mutations: { setName: (state) => {...} },
modules: {
moduleA: {
state: {},
muttations: { setName: (state) => {...} },
}
}
})
我们看到,全局空间和moduleA
都定义了名为setName
的mutation
,并且moduleA
没有启用单独的命名空间,因此下面的commit会导致两个都被触发:
this.$store.commit('setName', name);
而如果为moduleA
启用单独的命名空间,moduleA
就被高度封装了,上面向全局空间提交的commit不会再对moduleA生效。此时再想向moduleA提交一个commit必须这样:
this.$store.moduleA.commit('setName', name);
是否启用单独的命名空间要依具体的需求而定。
如果需要用辅助函数将模块内的state、getters、mutations和actions映射到组件内,需要用到Vuex提供的createNamespacedHelpers
方法:
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapGetters, mapMutations, mapActions} =
createNamespacedHelpers('moduleA');
...
computed: {
...mapState([...]),
...mapGetters([...])
},
methods: {
...mapMutations([...]),
...mapActions([...])
}
上面的辅助函数不一定要全部导入,比如当前组件只需要moduleA的name和对应的mutation:setName,就可以这样写:
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapMutations} =
createNamespacedHelpers('moduleA');
...
computed: {
...mapState(['name']),
// 其他计算属性
},
methods: {
...mapMutations(['setName']),
// 其他method
}
在模块内部,getters、mutations和actions接受的第一个参数与全局作用域不同。
对于getters,它的第一个参数是当前模块的state,而不是根state。如果你需要在getters内访问根state或根getters,可以作为第三、四个参数传入:
getters: {
name(state, getters, rootState, rootGetters){
...
}
}
对于mutations,它只能访问当前模块的state。这就意味着你不能用当前模块内的mutation去修改根状态。
对于actions,它接收的第一个参数不再是全局的store对象,而是当前模块对应的上下文对象context
。你可以通过它的rootState
属性来访问根状态,以及它的rootGetters
访问根getters
:
const moduleA = {
// ...
actions: {
actionA ({ state, commit, rootState, rootGetters }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
在模块内可以注册全局的action:
modules: {
moduleA: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... }
}
}
}
}
它会在向全局store派发action时被调用,但是却可以拿到当前模块的上下文对象(当然该对象的rootState,rootGetters也可以访问到全局state和getters)。
store在创建完成后仍然可以动态注册模块:
import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
动态注册的模块可以用store.unregisterModule('myModule')
的语法卸载,但是该语法不能卸载静态模块(即初始化时创建的模块)。
3. 项目结构
Vuex的store是全局唯一的,因此一般直接在src下维护。
如果没有使用模块,而全局状态比较复杂,可以把state、getters、mutations和actions单独提出来,在index中整合,如:
src
|- store
|- index.js
|- state.js
|- getters.js
|- mutations
|- actions
index.js一般这样写:
import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state'
import { getters} from './getters'
import { mutations} from './mutations'
import { actions} from './actions'
Vue.use(Vuex);
export default Vuex.Store({
state,
getters,
mutations,
actions
})
main.js中这样引入store:
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).mount('#app')
当然,如果store中维护的变量很少,可以直接在index.js中定义这些属性。
如果项目的store很复杂,需要用模块维护,一般会用一个文件夹对模块单独维护:
src
|- store
|- modules
|- moduleA.js
|- moduleB.js
|- index.js
index.js需要这样改写:
import Vue from 'vue'
import Vuex from 'vuex'
import { moduleA } from './modules/moduleA'
import { moduleB } from './modules/moduleB'
Vue.use(Vuex);
export default Vuex.Store({
state: {},
...
modules: {
moduleA,
moduleB
}
})
注意,即使使用模块,你也可以定义全局state、getters等,如果它足够复杂,也可以提取到外部文件,这完全视情况而定。
二、Vuex架构简介
1. Vue.use(Vuex)做了什么
这一部分我们会结合少量的Vuex源码,介绍Vuex工作的大致原理。
首先我们来看Vue.use(Vuex)
做了什么。
我们知道,Vue.use
是注册第三方组件的一般方法,它会去调用组件提供的install方法将组件注册到Vue上,下面看Vuex
提供的install方法:
function install (_Vue) {
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
applyMixin(Vue);
}
前面的例行检查可以忽略,Vuex真正的注册逻辑就是调用applyMixin
方法,我们看它的实现:
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
} else {
... // 兼容Vue 2.0以前的代码,可以忽略
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
}
在Vue 2.0以上的版本(2.0以下的实现是等价的,我们可以跳过),Vuex通过Vue.mixin({ beforeCreate: vuexInit })
向每个组件的beforeCreate
生命周期钩子中混入了一个方法:vueInit
。也就是说,Vue在初始化任何一个组件,并且经历完beforeCreate
这个生命周期后,都会执行这个vueInit
函数。
接着来看vueInit
的实现。它首先检查当前实例的$options是否包含store属性,这里的store属性就是我们在下面的初始化语句传入的。
new Vue({
store,
...
})
如果存在store属性,Vuex会将其挂载到当前组件对象的$store属性上,供组件访问。
也就是说,如果在new Vue时传入了store对象,那么在创建每个组件,并且初始化到beforeCreate
这个生命周期时,Vuex向Vue混入的vueInit
钩子函数就会被调用,它会检查options是否传入了store对象,如果是,就添加到this.$store属性上(如果store是函数,会先执行它)。
这样,我们的store对象就成了组件对象的$store属性。
下面我们来看const store = new Vuex.Store({ ... })
做了什么。
2. new Vuex.Store({ … })做了什么
Store是Vuex提供的构造函数,用于构造Store实例,它的实现如下:
var Store = function Store (options) {
... // 例行检查,如Vue和Vuex是否安装,是否通过new调用等
// store internal state
this._committing = false;
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options);
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
this._watcherVM = new Vue();
this._makeLocalGettersCache = Object.create(null);
// bind commit and dispatch to self
var store = this;
var ref = this;
var dispatch = ref.dispatch;
var commit = ref.commit;
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
};
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
};
// strict mode
this.strict = strict;
var state = this._modules.root.state;
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root);
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state);
// apply plugins
plugins.forEach(function (plugin) { return plugin(this$1); });
var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
if (useDevtools) {
devtoolPlugin(this);
}
};
跳过最前面的例行检查。首先,Vuex将传入的参数接收进来,保存在对应的属性内,同时定义一些用于管理订阅者、模块等的内部属性。
然后定义了触发mutation和action的commit和dispatch实例方法。
接着是初始化模块(installModule),每个模块基本上也可以看做一个store对象。
再接着是构建Vuex的响应系统(resetStoreVM)。到目前为止,Vuex并没有接入Vue的响应式系统,因此它的state变化并不会带来响应式效果,Vuex没有重新实现响应式系统,而是像下面一样用state和getters创建一个新的Vue实例:
function resetStoreVM (store, state, hot) {
...
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
...
}
plugins.forEach
语句是在安装Vuex插件,Vuex的插件一般是注册commit和dispatch事件的回调函数。
最后,devtoolPlugin
是将Vuex注册到Vue的调试工具devtools。
除了初始化之外,Vuex还定义了一些有用的原型函数,如Store.prototype.subscribe
,它可以向commit事件注册回调函数,在插件开发时非常有用,我们这里不再详细介绍,想要了解的可以去阅读Vuex源码。
总结
Vuex的使用不算特别复杂,源码逻辑也较为清晰(ps:本人只阅读了大致结构),而且在Vue开发中占有很重要的地位,因此作为Vue的开发者,认真学习它还是很有必要的。