Vuex 初始化这一节我们主要来分析 Vuex 的初始化过程,它包括安装、Store 实例化过程 2 个方面。
安装过程比较简单,下图是一个简单的关于实例化的思维导图。
安装当我们在代码中通过 import Vuex from 'vuex'
的时候,实际上引用的是一个对象,它的定义在 src/index.js
中:
1 2 3 4 5 6 7 8 9 10 export default { Store , install, version : '__VERSION__' , mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
和 Vue-Router 一样,Vuex 也同样存在一个静态的 install
方法,它的定义在 src/store.js
中:
1 2 3 4 5 6 7 8 9 10 11 12 export function install (_Vue ) { if (Vue && _Vue === Vue ) { if (process.env .NODE_ENV !== 'production' ) { console .error ( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin (Vue ) }
install
的逻辑很简单,把传入的 _Vue
赋值给 Vue
并执行了 applyMixin(Vue)
方法,它的定义在 src/mixin.js
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 export default function (Vue ) { const version = Number (Vue .version .split ('.' )[0 ]) if (version >= 2 ) { Vue .mixin ({ beforeCreate : vuexInit }) } else { const _init = Vue .prototype ._init Vue .prototype ._init = function (options = {} ) { options.init = options.init ? [vuexInit].concat (options.init ) : vuexInit _init.call (this , options) } } function vuexInit ( ) { const options = this .$options 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 } } }
applyMixin
就是这个 export default function
,它还兼容了 Vue 1.0 的版本,这里我们只关注 Vue 2.0 以上版本的逻辑,它其实就全局混入了一个 beforeCreate
钩子函数,它的实现非常简单,就是把 options.store
保存在所有组件的 this.$store
中,这个 options.store
就是我们在实例化 Store
对象的实例,稍后我们会介绍,这也是为什么我们在组件中可以通过 this.$store
访问到这个实例。
Store 实例化我们在 import Vuex
之后,会实例化其中的 Store
对象,返回 store
实例并传入 new Vue
的 options
中,也就是我们刚才提到的 options.store
.
举个简单的例子,如下:
1 2 3 4 5 6 7 8 export default new Vuex .Store ({ actions, getters, state, mutations, modules })
Store
对象的构造函数接收一个对象参数,它包含 actions
、getters
、state
、mutations
、modules
等 Vuex 的核心概念,它的定义在 src/store.js
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 export class Store { constructor (options = {} ) { if (!Vue && typeof window !== 'undefined' && window .Vue ) { install (window .Vue ) } if (process.env .NODE_ENV !== 'production' ) { assert (Vue , `must call Vue.use(Vuex) before creating a store instance.` ) assert (typeof Promise !== 'undefined' , `vuex requires a Promise polyfill in this browser.` ) assert (this instanceof Store , `Store must be called with the new operator.` ) } const { plugins = [], strict = false } = options 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 () const store = this const { dispatch, commit } = this 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) } this .strict = strict const state = this ._modules .root .state installModule (this , state, [], this ._modules .root ) resetStoreVM (this , state) plugins.forEach (plugin => plugin (this )) if (Vue .config .devtools ) { devtoolPlugin (this ) } } }
我们把 Store
的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm
,接下来我们来分析这 3 部分的实现。
初始化模块在分析模块初始化之前,我们先来了解一下模块对于 Vuex 的意义:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store
对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store
分割成模块(module)。每个模块拥有自己的 state
、mutation
、action
、getter
,甚至是嵌套子模块——从上至下进行同样方式的分割:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const moduleA = { state : { ... }, mutations : { ... }, actions : { ... }, getters : { ... } } const moduleB = { state : { ... }, mutations : { ... }, actions : { ... }, getters : { ... }, } const store = new Vuex .Store ({ modules : { a : moduleA, b : moduleB } }) store.state .a store.state .b
所以从数据结构上来看,模块的设计就是一个树型结构,store
本身可以理解为一个 root module
,它下面的 modules
就是子模块,Vuex 需要完成这颗树的构建,构建过程的入口就是:
1 this ._modules = new ModuleCollection (options)
ModuleCollection
的定义在 src/module/module-collection.js
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 export default class ModuleCollection { constructor (rawRootModule ) { this .register ([], rawRootModule, false ) } get (path) { return path.reduce ((module , key ) => { return module .getChild (key) }, this .root ) } getNamespace (path) { let module = this .root return path.reduce ((namespace, key ) => { module = module .getChild (key) return namespace + (module .namespaced ? key + '/' : '' ) }, '' ) } update (rawRootModule) { update ([], this .root , rawRootModule) } register (path, rawModule, runtime = true ) { if (process.env .NODE_ENV !== 'production' ) { assertRawModule (path, rawModule) } const newModule = new Module (rawModule, runtime) if (path.length === 0 ) { this .root = newModule } else { const parent = this .get (path.slice (0 , -1 )) parent.addChild (path[path.length - 1 ], newModule) } if (rawModule.modules ) { forEachValue (rawModule.modules , (rawChildModule, key ) => { this .register (path.concat (key), rawChildModule, runtime) }) } } unregister (path) { const parent = this .get (path.slice (0 , -1 )) const key = path[path.length - 1 ] if (!parent.getChild (key).runtime ) return parent.removeChild (key) } }
ModuleCollection
实例化的过程就是执行了 register
方法, register
接收 3 个参数,其中 path
表示路径,因为我们整体目标是要构建一颗模块树,path
是在构建树的过程中维护的路径;rawModule
表示定义模块的原始配置;runtime
表示是否是一个运行时创建的模块。
register
方法首先通过 const newModule = new Module(rawModule, runtime)
创建了一个 Module
的实例,Module
是用来描述单个模块的类,它的定义在 src/module/module.js
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 export default class Module { constructor (rawModule, runtime ) { this .runtime = runtime this ._children = Object .create (null ) this ._rawModule = rawModule const rawState = rawModule.state this .state = (typeof rawState === 'function' ? rawState () : rawState) || {} } get namespaced () { return !!this ._rawModule .namespaced } addChild (key, module ) { this ._children [key] = module } removeChild (key) { delete this ._children [key] } getChild (key) { return this ._children [key] } update (rawModule) { this ._rawModule .namespaced = rawModule.namespaced if (rawModule.actions ) { this ._rawModule .actions = rawModule.actions } if (rawModule.mutations ) { this ._rawModule .mutations = rawModule.mutations } if (rawModule.getters ) { this ._rawModule .getters = rawModule.getters } } forEachChild (fn) { forEachValue (this ._children , fn) } forEachGetter (fn) { if (this ._rawModule .getters ) { forEachValue (this ._rawModule .getters , fn) } } forEachAction (fn) { if (this ._rawModule .actions ) { forEachValue (this ._rawModule .actions , fn) } } forEachMutation (fn) { if (this ._rawModule .mutations ) { forEachValue (this ._rawModule .mutations , fn) } } }
来看一下 Module
的构造函数,对于每个模块而言,this._rawModule
表示模块的配置,this._children
表示它的所有子模块,this.state
表示这个模块定义的 state
。
回到 register
,那么在实例化一个 Module
后,判断当前的 path
的长度如果为 0,则说明它是一个根模块,所以把 newModule
赋值给了 this.root
,否则就需要建立父子关系了:
1 2 const parent = this .get (path.slice (0 , -1 ))parent.addChild (path[path.length - 1 ], newModule)
我们先大体上了解它的逻辑:首先根据路径获取到父模块,然后再调用父模块的 addChild
方法建立父子关系。
register
的最后一步,就是遍历当前模块定义中的所有 modules
,根据 key
作为 path
,递归调用 register
方法,这样我们再回过头看一下建立父子关系的逻辑,首先执行了 this.get(path.slice(0, -1)
方法:
1 2 3 4 5 get (path) { return path.reduce ((module , key ) => { return module .getChild (key) }, this .root ) }
传入的 path
是它的父模块的 path
,然后从根模块开始,通过 reduce
方法一层层去找到对应的模块,查找的过程中,执行的是 module.getChild(key)
方法:
1 2 3 getChild (key) { return this ._children [key] }
其实就是返回当前模块的 _children
中对应 key
的模块,那么每个模块的 _children
是如何添加的呢,是通过执行 parent.addChild(path[path.length - 1], newModule)
方法:
1 2 3 addChild (key, module ) { this ._children [key] = module }
所以说对于 root module
的下一层 modules
来说,它们的 parent
就是 root module
,那么他们就会被添加的 root module
的 _children
中。每个子模块通过路径找到它的父模块,然后通过父模块的 addChild
方法建立父子关系,递归执行这样的过程,最终就建立一颗完整的模块树。
安装模块初始化模块后,执行安装模块的相关逻辑,它的目标就是对模块中的 state
、getters
、mutations
、actions
做初始化工作,它的入口代码是:
1 2 const state = this ._modules .root .state installModule (this , state, [], this ._modules .root )
来看一下 installModule
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function installModule (store, rootState, path, module , hot ) { const isRoot = !path.length const namespace = store._modules .getNamespace (path) if (module .namespaced ) { store._modulesNamespaceMap [namespace] = module } if (!isRoot && !hot) { const parentState = getNestedState (rootState, path.slice (0 , -1 )) const moduleName = path[path.length - 1 ] store._withCommit (() => { Vue .set (parentState, moduleName, module .state ) }) } const local = module .context = makeLocalContext (store, namespace, path) module .forEachMutation ((mutation, key ) => { const namespacedType = namespace + key registerMutation (store, namespacedType, mutation, local) }) module .forEachAction ((action, key ) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction (store, type, handler, local) }) module .forEachGetter ((getter, key ) => { const namespacedType = namespace + key registerGetter (store, namespacedType, getter, local) }) module .forEachChild ((child, key ) => { installModule (store, rootState, path.concat (key), child, hot) }) }
installModule
方法支持 5 个参数,store
表示 root store
;state
表示 root state
;path
表示模块的访问路径;module
表示当前的模块,hot
表示是否是热更新。
接下来看函数逻辑,这里涉及到了命名空间的概念,默认情况下,模块内部的 action
、mutation
和 getter
是注册在全局命名空间的——这样使得多个模块能够对同一 mutation
或 action
作出响应。如果我们希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const store = new Vuex .Store ({ modules : { account : { namespaced : true , state : { ... }, getters : { isAdmin () { ... } }, actions : { login () { ... } }, mutations : { login () { ... } }, modules : { myPage : { state : { ... }, getters : { profile () { ... } } }, posts : { namespaced : true , state : { ... }, getters : { popular () { ... } } } } } } })
回到 installModule
方法,我们首先根据 path
获取 namespace
:
1 const namespace = store._modules .getNamespace (path)
getNamespace
的定义在 src/module/module-collection.js
中:
1 2 3 4 5 6 7 getNamespace (path) { let module = this .root return path.reduce ((namespace, key ) => { module = module .getChild (key) return namespace + (module .namespaced ? key + '/' : '' ) }, '' ) }
从 root module
开始,通过 reduce
方法一层层找子模块,如果发现该模块配置了 namespaced
为 true,则把该模块的 key
拼到 namesapce
中,最终返回完整的 namespace
字符串。
回到 installModule
方法,接下来把 namespace
对应的模块保存下来,为了方便以后能根据 namespace
查找模块:
1 2 3 if (module .namespaced ) { store._modulesNamespaceMap [namespace] = module }
接下来判断非 root module
且非 hot
的情况执行一些逻辑,我们稍后再看。
接着是很重要的逻辑,构造了一个本地上下文环境:
1 const local = module .context = makeLocalContext (store, namespace, path)
来看一下 makeLocalContext
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 function makeLocalContext (store, namespace, path ) { const noNamespace = namespace === '' const local = { dispatch : noNamespace ? store.dispatch : (_type, _payload, _options ) => { const args = unifyObjectStyle (_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root ) { type = namespace + type if (process.env .NODE_ENV !== 'production' && !store._actions [type]) { console .error (`[vuex] unknown local action type: ${args.type} , global type: ${type} ` ) return } } return store.dispatch (type, payload) }, commit : noNamespace ? store.commit : (_type, _payload, _options ) => { const args = unifyObjectStyle (_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root ) { type = namespace + type if (process.env .NODE_ENV !== 'production' && !store._mutations [type]) { console .error (`[vuex] unknown local mutation type: ${args.type} , global type: ${type} ` ) return } } store.commit (type, payload, options) } } Object .defineProperties (local, { getters : { get : noNamespace ? () => store.getters : () => makeLocalGetters (store, namespace) }, state : { get : () => getNestedState (store.state , path) } }) return local }
makeLocalContext
支持 3 个参数相关,store
表示 root store
;namespace
表示模块的命名空间,path
表示模块的 path
。
该方法定义了 local
对象,对于 dispatch
和 commit
方法,如果没有 namespace
,它们就直接指向了 root store
的 dispatch
和 commit
方法,否则会创建方法,把 type
自动拼接上 namespace
,然后执行 store
上对应的方法。
对于 getters
而言,如果没有 namespace
,则直接返回 root store
的 getters
,否则返回 makeLocalGetters(store, namespace)
的返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function makeLocalGetters (store, namespace ) { const gettersProxy = {} const splitPos = namespace.length Object .keys (store.getters ).forEach (type => { if (type.slice (0 , splitPos) !== namespace) return const localType = type.slice (splitPos) Object .defineProperty (gettersProxy, localType, { get : () => store.getters [type], enumerable : true }) }) return gettersProxy }
makeLocalGetters
首先获取了 namespace
的长度,然后遍历 root store
下的所有 getters
,先判断它的类型是否匹配 namespace
,只有匹配的时候我们从 namespace
的位置截取后面的字符串得到 localType
,接着用 Object.defineProperty
定义了 gettersProxy
,获取 localType
实际上是访问了 store.getters[type]
。
回到 makeLocalContext
方法,再来看一下对 state
的实现,它的获取则是通过 getNestedState(store.state, path)
方法:
1 2 3 4 5 function getNestedState (state, path ) { return path.length ? path.reduce ((state, key ) => state[key], state) : state }
getNestedState
逻辑很简单,从 root state
开始,通过 path.reduce
方法一层层查找子模块 state
,最终找到目标模块的 state
。
那么构造完 local
上下文后,我们再回到 installModule
方法,接下来它就会遍历模块中定义的 mutations
、actions
、getters
,分别执行它们的注册工作,它们的注册逻辑都大同小异。
1 2 3 4 5 6 7 8 9 10 11 module .forEachMutation ((mutation, key ) => { const namespacedType = namespace + key registerMutation (store, namespacedType, mutation, local) }) function registerMutation (store, type, handler, local ) { const entry = store._mutations [type] || (store._mutations [type] = []) entry.push (function wrappedMutationHandler (payload ) { handler.call (store, local.state , payload) }) }
首先遍历模块中的 mutations
的定义,拿到每一个 mutation
和 key
,并把 key
拼接上 namespace
,然后执行 registerMutation
方法。该方法实际上就是给 root store
上的 _mutations[types]
添加 wrappedMutationHandler
方法,该方法的具体实现我们之后会提到。注意,同一 type
的 _mutations
可以对应多个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 module .forEachAction ((action, key ) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction (store, type, handler, local) }) function registerAction (store, type, handler, local ) { const entry = store._actions [type] || (store._actions [type] = []) entry.push (function wrappedActionHandler (payload, cb ) { let res = handler.call (store, { dispatch : local.dispatch , commit : local.commit , getters : local.getters , state : local.state , rootGetters : store.getters , rootState : store.state }, payload, cb) if (!isPromise (res)) { res = Promise .resolve (res) } if (store._devtoolHook ) { return res.catch (err => { store._devtoolHook .emit ('vuex:error' , err) throw err }) } else { return res } }) }
首先遍历模块中的 actions
的定义,拿到每一个 action
和 key
,并判断 action.root
,如果否的情况把 key
拼接上 namespace
,然后执行 registerAction
方法。该方法实际上就是给 root store
上的 _actions[types]
添加 wrappedActionHandler
方法,该方法的具体实现我们之后会提到。注意,同一 type
的 _actions
可以对应多个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module .forEachGetter ((getter, key ) => { const namespacedType = namespace + key registerGetter (store, namespacedType, getter, local) }) function registerGetter (store, type, rawGetter, local ) { if (store._wrappedGetters [type]) { if (process.env .NODE_ENV !== 'production' ) { console .error (`[vuex] duplicate getter key: ${type} ` ) } return } store._wrappedGetters [type] = function wrappedGetter (store ) { return rawGetter ( local.state , local.getters , store.state , store.getters ) } }
首先遍历模块中的 getters
的定义,拿到每一个 getter
和 key
,并把 key
拼接上 namespace
,然后执行 registerGetter
方法。该方法实际上就是给 root store
上的 _wrappedGetters[key]
指定 wrappedGetter
方法,该方法的具体实现我们之后会提到。注意,同一 type
的 _wrappedGetters
只能定义一个。
再回到 installModule
方法,最后一步就是遍历模块中的所有子 modules
,递归执行 installModule
方法:
1 2 3 module .forEachChild ((child, key ) => { installModule (store, rootState, path.concat (key), child, hot) })
之前我们忽略了非 root module
下的 state
初始化逻辑,现在来看一下:
1 2 3 4 5 6 7 if (!isRoot && !hot) { const parentState = getNestedState (rootState, path.slice (0 , -1 )) const moduleName = path[path.length - 1 ] store._withCommit (() => { Vue .set (parentState, moduleName, module .state ) }) }
之前我们提到过 getNestedState
方法,它是从 root state
开始,一层层根据模块名能访问到对应 path
的 state
,那么它每一层关系的建立实际上就是通过这段 state
的初始化逻辑。store._withCommit
方法我们之后再介绍。
所以 installModule
实际上就是完成了模块下的 state
、getters
、actions
、mutations
的初始化工作,并且通过递归遍历的方式,就完成了所有子模块的安装工作。
初始化 store._vm
Store
实例化的最后一步,就是执行初始化 store._vm
的逻辑,它的入口代码是:
1 resetStoreVM (this , state)
来看一下 resetStoreVM
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function resetStoreVM (store, state, hot ) { const oldVm = store._vm store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} forEachValue (wrappedGetters, (fn, key ) => { computed[key] = () => fn (store) Object .defineProperty (store.getters , key, { get : () => store._vm [key], enumerable : true }) }) const silent = Vue .config .silent Vue .config .silent = true store._vm = new Vue ({ data : { $$state : state }, computed }) Vue .config .silent = silent if (store.strict ) { enableStrictMode (store) } if (oldVm) { if (hot) { store._withCommit (() => { oldVm._data .$$state = null }) } Vue .nextTick (() => oldVm.$destroy()) } }
resetStoreVM
的作用实际上是想建立 getters
和 state
的联系,因为从设计上 getters
的获取就依赖了 state
,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 computed
计算属性来实现。
resetStoreVM
首先遍历了 _wrappedGetters
获得每个 getter
的函数 fn
和 key
,然后定义了 computed[key] = () => fn(store)
。我们之前提到过 _wrappedGetters
的初始化过程,这里 fn(store)
相当于执行如下方法:
1 2 3 4 5 6 7 8 store._wrappedGetters [type] = function wrappedGetter (store ) { return rawGetter ( local.state , local.getters , store.state , store.getters ) }
返回的就是 rawGetter
的执行函数,rawGetter
就是用户定义的 getter
函数,它的前 2 个参数是 local state
和 local getters
,后 2 个参数是 root state
和 root getters
。
接着实例化一个 Vue 实例 store._vm
,并把 computed
传入:
1 2 3 4 5 6 store._vm = new Vue ({ data : { $$state : state }, computed })
我们发现 data
选项里定义了 $$state
属性,而我们访问 store.state
的时候,实际上会访问 Store
类上定义的 state
的 get
方法:
1 2 3 get state () { return this ._vm ._data .$$state }
它实际上就访问了 store._vm._data.$$state
。那么 getters
和 state
是如何建立依赖逻辑的呢,我们再看这段代码逻辑:
1 2 3 4 5 6 7 8 forEachValue (wrappedGetters, (fn, key ) => { computed[key] = () => fn (store) Object .defineProperty (store.getters , key, { get : () => store._vm [key], enumerable : true }) })
当我根据 key
访问 store.getters
的某一个 getter
的时候,实际上就是访问了 store._vm[key]
,也就是 computed[key]
,在执行 computed[key]
对应的函数的时候,会执行 rawGetter(local.state,...)
方法,那么就会访问到 store.state
,进而访问到 store._vm._data.$$state
,这样就建立了一个依赖关系。当 store.state
发生变化的时候,下一次再访问 store.getters
的时候会重新计算。
我们再来看一下 strict mode
的逻辑:
1 2 3 4 5 6 7 8 9 10 11 if (store.strict ) { enableStrictMode (store) } function enableStrictMode (store ) { store._vm .$watch(function ( ) { return this ._data .$$state }, () => { if (process.env .NODE_ENV !== 'production' ) { assert (store._committing , `Do not mutate vuex store state outside mutation handlers.` ) } }, { deep : true , sync : true }) }
当严格模式下,store._vm
会添加一个 wathcer
来观测 this._data.$$state
的变化,也就是当 store.state
被修改的时候, store._committing
必须为 true,否则在开发阶段会报警告。store._committing
默认值是 false
,那么它什么时候会 true 呢,Store
定义了 _withCommit
实例方法:
1 2 3 4 5 6 _withCommit (fn) { const committing = this ._committing this ._committing = true fn () this ._committing = committing }
它就是对 fn
包装了一个环境,确保在 fn
中执行任何逻辑的时候 this._committing = true
。所以外部任何非通过 Vuex 提供的接口直接操作修改 state
的行为都会在开发阶段触发警告。
总结那么至此,Vuex 的初始化过程就分析完毕了,除了安装部分,我们重点分析了 Store
的实例化过程。我们要把 store
想象成一个数据仓库,为了更方便的管理仓库,我们把一个大的 store
拆成一些 modules
,整个 modules
是一个树型结构。每个 module
又分别定义了 state
,getters
,mutations
、actions
,我们也通过递归遍历模块的方式都完成了它们的初始化。为了 module
具有更高的封装度和复用性,还定义了 namespace
的概念。最后我们还定义了一个内部的 Vue
实例,用来建立 state
到 getters
的联系,并且可以在严格模式下监测 state
的变化是不是来自外部,确保改变 state
的唯一途径就是显式地提交 mutation
。