分析 Vuex 的初始化过程,它包括安装、Store 实例化过程 2 个方面
安装
当在代码中通过 import Vuex from ‘vuex’ 的时候,实际上引用的是一个对象,它的定义在 src/index.js 中
import { Store, install } from './store'import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'import createLogger from './plugins/logger'export default {Store,install,version: '__VERSION__',mapState,mapMutations,mapGetters,mapActions,createNamespacedHelpers,createLogger}
和 Vue-Router 一样,Vuex 也同样存在一个静态的 install 方法,它的定义在 src/store.js 中
export function install (_Vue) {if (Vue && _Vue === Vue) {if (__DEV__) {console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.')}return}// 把传入的_Vue赋值给Vue并执行applyMixin(Vue)方法Vue = _VueapplyMixin(Vue)}
applyMixin定义在 src/mixin.js 中
// applyMixin就是导出的匿名函数,兼容Vue1.x版本export default function (Vue) {const version = Number(Vue.version.split('.')[0])if (version >= 2) {// Vue2.x版本 全局混入一个beforeCreate钩子函数Vue.mixin({ beforeCreate: vuexInit })} else { // Vue1.x版本// override init and inject vuex init procedure// for 1.x backwards compatibility.const _init = Vue.prototype._initVue.prototype._init = function (options = {}) {options.init = options.init? [vuexInit].concat(options.init): vuexInit_init.call(this, options)}}/*** Vuex init hook, injected into each instances init hooks list.*/function vuexInit () {const options = this.$options// store injectionif (options.store) {// 把options.store保存在所有组件的this.$store中 options.store就是在实例化Store对象的实例,也是为什么在组件中可以通过this.$store访问到这个实例this.$store = typeof options.store === 'function'? options.store(): options.store} else if (options.parent && options.parent.$store) {this.$store = options.parent.$store}}}
Store 实例化
在 import Vuex 之后,会实例化其中的 Store 对象,返回 store 实例并传入 new Vue 的 options 中,也就是刚才提到的 options.store
例子
export default new Vuex.Store({actions,getters,state,mutations,modules// ...})
Store 对象的构造函数接收一个对象参数,它包含 actions、getters、state、mutations、modules 等 Vuex 的核心概念,它的定义在 src/store.js 中
export class Store {constructor (options = {}) {// Auto install if it is not done yet and `window` has `Vue`.// To allow users to avoid auto-installation in some cases,// this code should be placed here. See #731if (!Vue && typeof window !== 'undefined' && window.Vue) {install(window.Vue)}if (__DEV__) {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// store internal statethis._committing = falsethis._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 selfconst store = thisconst { dispatch, commit } = thisthis.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 modethis.strict = strictconst state = this._modules.root.state// init root module.// this also recursively registers all sub-modules// and collects all module getters inside this._wrappedGettersinstallModule(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 pluginsplugins.forEach(plugin => plugin(this))const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtoolsif (useDevtools) {devtoolPlugin(this)}}get state () {return this._vm._data.$$state}set state (v) {if (__DEV__) {assert(false, `use store.replaceState() to explicit replace store state.`)}}commit (_type, _payload, _options) {// check object-style commitconst {type,payload,options} = unifyObjectStyle(_type, _payload, _options)const mutation = { type, payload }const entry = this._mutations[type]if (!entry) {if (__DEV__) {console.error(`[vuex] unknown mutation type: ${type}`)}return}this._withCommit(() => {entry.forEach(function commitIterator (handler) {handler(payload)})})this._subscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.forEach(sub => sub(mutation, this.state))if (__DEV__ &&options && options.silent) {console.warn(`[vuex] mutation type: ${type}. Silent option has been removed. ` +'Use the filter functionality in the vue-devtools')}}dispatch (_type, _payload) {// check object-style dispatchconst {type,payload} = unifyObjectStyle(_type, _payload)const action = { type, payload }const entry = this._actions[type]if (!entry) {if (__DEV__) {console.error(`[vuex] unknown action type: ${type}`)}return}try {this._actionSubscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.filter(sub => sub.before).forEach(sub => sub.before(action, this.state))} catch (e) {if (__DEV__) {console.warn(`[vuex] error in before action subscribers: `)console.error(e)}}const result = entry.length > 1? Promise.all(entry.map(handler => handler(payload))): entry[0](payload)return new Promise((resolve, reject) => {result.then(res => {try {this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))} catch (e) {if (__DEV__) {console.warn(`[vuex] error in after action subscribers: `)console.error(e)}}resolve(res)}, error => {try {this._actionSubscribers.filter(sub => sub.error).forEach(sub => sub.error(action, this.state, error))} catch (e) {if (__DEV__) {console.warn(`[vuex] error in error action subscribers: `)console.error(e)}}reject(error)})})}subscribe (fn, options) {return genericSubscribe(fn, this._subscribers, options)}subscribeAction (fn, options) {const subs = typeof fn === 'function' ? { before: fn } : fnreturn genericSubscribe(subs, this._actionSubscribers, options)}watch (getter, cb, options) {if (__DEV__) {assert(typeof getter === 'function', `store.watch only accepts a function.`)}return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)}replaceState (state) {this._withCommit(() => {this._vm._data.$$state = state})}registerModule (path, rawModule, options = {}) {if (typeof path === 'string') path = [path]if (__DEV__) {assert(Array.isArray(path), `module path must be a string or an Array.`)assert(path.length > 0, 'cannot register the root module by using registerModule.')}this._modules.register(path, rawModule)installModule(this, this.state, path, this._modules.get(path), options.preserveState)// reset store to update getters...resetStoreVM(this, this.state)}unregisterModule (path) {if (typeof path === 'string') path = [path]if (__DEV__) {assert(Array.isArray(path), `module path must be a string or an Array.`)}this._modules.unregister(path)this._withCommit(() => {const parentState = getNestedState(this.state, path.slice(0, -1))Vue.delete(parentState, path[path.length - 1])})resetStore(this)}hasModule (path) {if (typeof path === 'string') path = [path]if (__DEV__) {assert(Array.isArray(path), `module path must be a string or an Array.`)}return this._modules.isRegistered(path)}hotUpdate (newOptions) {this._modules.update(newOptions)resetStore(this, true)}_withCommit (fn) {const committing = this._committingthis._committing = truefn()this._committing = committing}}
把 Store 的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm
初始化模块
模块对于 Vuex 的意义:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿
为了解决以上问题,Vuex 允许将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块——从上至下进行同样方式的分割
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 // -> moduleA 的状态store.state.b // -> moduleB 的状态
所以从数据结构上来看,模块的设计就是一个树型结构,store 本身可以理解为一个 root module,它下面的 modules 就是子模块,Vuex 需要完成这颗树的构建,构建过程的入口就是
this._modules = new ModuleCollection(options)
ModuleCollection 的定义在 src/module/module-collection.js 中
export default class ModuleCollection {constructor (rawRootModule) {// register root module (Vuex.Store options)// ModuleCollection实例化的过程就是执行了register方法,三个参数// path 表示路径,因为整体目标是要构建一颗模块树,path是在构建树的过程中维护的路径// rawModule 表示定义模块的原始配置// runtime 表示是否是一个运行时创建的模块this.register([], rawRootModule, false)}// 传入的path是它父模块的pathget (path) {// 从根模块开始,通过reduce方法一层层去找到对应的模块// 查找的过程中执行的是module.getChild(key)方法return path.reduce((module, key) => {return module.getChild(key)}, this.root)}getNamespace (path) {let module = this.rootreturn 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 (__DEV__) {assertRawModule(path, rawModule)}// 创建一个Module的实例- Module是用来描述单个模块的库const newModule = new Module(rawModule, runtime)if (path.length === 0) { // 根模块this.root = newModule} else {// 建立父子关系// 根据路径获取到父模块const parent = this.get(path.slice(0, -1))// 再调用父模块的addChild方法创建父子关系parent.addChild(path[path.length - 1], newModule)}// register nested modulesif (rawModule.modules) {// 遍历当前模块定义中的所有modules,根据key作为path,递归调用register方法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]const child = parent.getChild(key)if (!child) {if (__DEV__) {console.warn(`[vuex] trying to unregister module '${key}', which is ` +`not registered`)}return}if (!child.runtime) {return}parent.removeChild(key)}isRegistered (path) {const parent = this.get(path.slice(0, -1))const key = path[path.length - 1]if (parent) {return parent.hasChild(key)}return false}}
Module是用来描述单个模块的库,它的定义在 src/module/module.js 中
// Base data struct for store's module, package with some attribute and methodexport default class Module {constructor (rawModule, runtime) {this.runtime = runtime// Store some children item// 所有子模块this._children = Object.create(null)// Store the origin module object which passed by programmer// 模块的配置this._rawModule = rawModuleconst rawState = rawModule.state// Store the origin module's state// 该模块定义的statethis.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) {// 返回当前模块的_children中对应key的模块return this._children[key]}hasChild (key) {return key in this._children}update (rawModule) {this._rawModule.namespaced = rawModule.namespacedif (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)}}}
所以说对于 root module 的下一层 modules 来说,它们的 parent 就是 root module,那么他们就会被添加的 root module 的 _children 中。每个子模块通过路径找到它的父模块,然后通过父模块的 addChild 方法建立父子关系,递归执行这样的过程,最终就建立一颗完整的模块树
安装模块
初始化模块后,执行安装模块的相关逻辑,它的目标就是对模块中的 state、getters、mutations、actions 做初始化工作,它的入口代码是
const state = this._modules.root.state// init root module.// this also recursively registers all sub-modules// and collects all module getters inside this._wrappedGettersinstallModule(this, state, [], this._modules.root)
installModule的定义
// store表示root store// state表示root state// path表示模块的访问路径// module表示当前的模块// hot表示是否是热更新function installModule (store, rootState, path, module, hot) {const isRoot = !path.length// 根据path获取namespaceconst namespace = store._modules.getNamespace(path)// register in namespace mapif (module.namespaced) {if (store._modulesNamespaceMap[namespace] && __DEV__) {console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)}// 把namespace对应的模块保存下来,方便以后能根据namespace查找模块store._modulesNamespaceMap[namespace] = module}// set state// 非root module下的state初始化逻辑if (!isRoot && !hot) {// getNestedState方法是从root state开始,一层层根据模块名能访问到对应path的state,那么它每一层关系的建立实际上就是通过这段state的初始化逻辑const parentState = getNestedState(rootState, path.slice(0, -1))const moduleName = path[path.length - 1]store._withCommit(() => {if (__DEV__) {if (moduleName in parentState) {console.warn(`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`)}}Vue.set(parentState, moduleName, module.state)})}// 构建一个本地上下文环境const local = module.context = makeLocalContext(store, namespace, path)// 遍历模块中定义的mutation、action、getter,分别执行注册工作// 遍历模块中的mutations的定义,拿到每一个mutation和 key,并把key拼接上 namespace,然后执行registerMutation方法module.forEachMutation((mutation, key) => {const namespacedType = namespace + keyregisterMutation(store, namespacedType, mutation, local)})// 遍历模块中的actions的定义,拿到每一个action和key,并判断action.root,如果否的情况把key拼接上namespace,然后执行registerAction方法module.forEachAction((action, key) => {const type = action.root ? key : namespace + keyconst handler = action.handler || actionregisterAction(store, type, handler, local)})// 遍历模块中的getters的定义,拿到每一个getter和key,并把key拼接上namespace,然后执行registerGetter方法module.forEachGetter((getter, key) => {const namespacedType = namespace + keyregisterGetter(store, namespacedType, getter, local)})// 遍历模块中的所有子modules,递归执行installModule方法module.forEachChild((child, key) => {installModule(store, rootState, path.concat(key), child, hot)})}
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应
如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
例如
const store = new Vuex.Store({modules: {account: {namespaced: true,// 模块内容(module assets)state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响getters: {isAdmin () { ... } // -> getters['account/isAdmin']},actions: {login () { ... } // -> dispatch('account/login')},mutations: {login () { ... } // -> commit('account/login')},// 嵌套模块modules: {// 继承父模块的命名空间myPage: {state: { ... },getters: {profile () { ... } // -> getters['account/profile']}},// 进一步嵌套命名空间posts: {namespaced: true,state: { ... },getters: {popular () { ... } // -> getters['account/posts/popular']}}}}}})
getNamespace定义在 src/module/module-collection.js 中
getNamespace (path) {// 从root module开始通过reduce方法一层层找子模块,如果发现该模块配置了namespaced为true,则把该模块的key拼到namespace中,最终返回完整的namespace字符串let module = this.rootreturn path.reduce((namespace, key) => {module = module.getChild(key)return namespace + (module.namespaced ? key + '/' : '')}, '')}
makeLocalContext
该方法定义了 local 对象,对于 dispatch 和 commit 方法,如果没有 namespace,它们就直接指向了 root store 的 dispatch 和 commit 方法,否则会创建方法,把 type 自动拼接上 namespace,然后执行 store 上对应的方法
// store表示root store// namespace表示模块的命名空间// path表示模块的pathfunction 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 } = argslet { type } = argsif (!options || !options.root) {type = namespace + typeif (__DEV__ && !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 } = argslet { type } = argsif (!options || !options.root) {type = namespace + typeif (__DEV__ && !store._mutations[type]) {console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)return}}store.commit(type, payload, options)}}// getters and state object must be gotten lazily// because they will be changed by vm updateObject.defineProperties(local, {// getters 如果没有namespace直接返回root store的getters,否则返回makeLocalGetters方法的返回值getters: {get: noNamespace? () => store.getters: () => makeLocalGetters(store, namespace)},// states 获取是通过getNestedState方法state: {get: () => getNestedState(store.state, path)}})return local}
makeLocalGetters
function makeLocalGetters (store, namespace) {if (!store._makeLocalGettersCache[namespace]) {const gettersProxy = {}// 获取了namespace的长度const splitPos = namespace.length// 遍历root store下所有的gettersObject.keys(store.getters).forEach(type => {// skip if the target getter is not match this namespace// 先判断它的类型的是否匹配到namespaceif (type.slice(0, splitPos) !== namespace) return// extract local getter type// 匹配到后从namespace的位置截取后面的字符串得到localTypeconst localType = type.slice(splitPos)// Add a port to the getters proxy.// Define as getter property because// we do not want to evaluate the getters in this time.// 定义了gettersProxy,获取localType实际上是访问了store.getters[type]Object.defineProperty(gettersProxy, localType, {get: () => store.getters[type],enumerable: true})})store._makeLocalGettersCache[namespace] = gettersProxy}return store._makeLocalGettersCache[namespace]}
getNestedState
function getNestedState (state, path) {// 从root state开始通过path.reduce方法一层层查找子模块state,最终找到目标模块的statereturn path.reduce((state, key) => state[key], state)}
registerMutation
同一type的_mutations可以对应多个方法
// 给root store上的_mutations[types]添加wrappedMutationHandler方法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)})}
registerAction
同一type的_actions可以对应多个方法
// 给root store上的_actions[types]添加wrappedActionHandler方法function registerAction (store, type, handler, local) {const entry = store._actions[type] || (store._actions[type] = [])entry.push(function wrappedActionHandler (payload) {let res = handler.call(store, {dispatch: local.dispatch,commit: local.commit,getters: local.getters,state: local.state,rootGetters: store.getters,rootState: store.state}, payload)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}})}
registerGetter
同一 type 的_wrappedGetters只能定义一个
// root store上的_wrappedGetters[key]指定wrappedGetter方法function registerGetter (store, type, rawGetter, local) {if (store._wrappedGetters[type]) {if (__DEV__) {console.error(`[vuex] duplicate getter key: ${type}`)}return}store._wrappedGetters[type] = function wrappedGetter (store) {return rawGetter(local.state, // local statelocal.getters, // local gettersstore.state, // root statestore.getters // root getters)}}
所以 installModule 实际上就是完成了模块下的 state、getters、actions、mutations 的初始化工作,并且通过递归遍历的方式,就完成了所有子模块的安装工作
初始化 store._vm
Store 实例化的最后一步,就是执行初始化 store._vm 的逻辑,它的入口代码是
// initialize the store vm, which is responsible for the reactivity// (also registers _wrappedGetters as computed properties)resetStoreVM(this, state)
resetStoreVM 的定义
resetStoreVM 的作用实际上是想建立 getters 和 state 的联系,因为从设计上 getters 的获取就依赖了 state ,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 computed 计算属性来实现
function resetStoreVM (store, state, hot) {const oldVm = store._vm// bind store public gettersstore.getters = {}// reset local getters cachestore._makeLocalGettersCache = Object.create(null)const wrappedGetters = store._wrappedGettersconst computed = {}// 遍历了wrappedGetters获得每个getter的函数fn和keyforEachValue(wrappedGetters, (fn, key) => {// use computed to leverage its lazy-caching mechanism// direct inline function use will lead to closure preserving oldVm.// using partial to return function with only arguments preserved in closure environment.// partial(fn, store)使用匿名函数去执行fn(store)computed[key] = partial(fn, store)Object.defineProperty(store.getters, key, {get: () => store._vm[key],enumerable: true // for local getters})})// use a Vue instance to store the state tree// suppress warnings just in case the user has added// some funky global mixinsconst silent = Vue.config.silentVue.config.silent = true// 实例化一个Vue实例store._vm,并把computed传入store._vm = new Vue({data: {$$state: state // 访问store.state时实际上会访问Store类上定义的state的get方法},computed})Vue.config.silent = silent// enable strict mode for new vm// 严格模式下if (store.strict) {enableStrictMode(store)}if (oldVm) {if (hot) {// dispatch changes in all subscribed watchers// to force getter re-evaluation for hot reloading.store._withCommit(() => {oldVm._data.$$state = null})}Vue.nextTick(() => oldVm.$destroy())}}
partial
export function partial (fn, arg) {return function () {return fn(arg)}}// fn(store)相当于执行store._wrappedGetters[type] = function wrappedGetter (store) {return rawGetter(local.state, // local statelocal.getters, // local gettersstore.state, // root statestore.getters // root getters)}
返回的就是 rawGetter 的执行函数,rawGetter 就是用户定义的 getter 函数
Store类上定义的state的get方法
get state () {return this._vm._data.$$state // 实际上访问了store._vm._data.$$state}
getters 和 state 是如何建立依赖逻辑的呢
forEachValue(wrappedGetters, (fn, key) => {// use computed to leverage its lazy-caching mechanism// direct inline function use will lead to closure preserving oldVm.// using partial to return function with only arguments preserved in closure environment.computed[key] = partial(fn, store)Object.defineProperty(store.getters, key, {get: () => store._vm[key],enumerable: true // for local getters})})
根据 key 访问 store.getters 的某一个 getter 的时候,实际上就是访问了 store._vm[key],也就是 computed[key],在执行 computed[key] 对应的函数的时候,会执行 rawGetter(local.state,…) 方法,那么就会访问到 store.state,进而访问到 store._vm._data.$$state,这样就建立了一个依赖关系。当 store.state 发生变化的时候,下一次再访问 store.getters 的时候会重新计算
enableStrictMode
function enableStrictMode (store) {store._vm.$watch(function () { return this._data.$$state }, () => {if (__DEV__) {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 实例方法
_withCommit (fn) {const committing = this._committingthis._committing = truefn()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
