二、模块初始化
在Store的constructor中,会执行一系列数据的初始化,其中会执行this._modules = new ModuleCollection(options),假设写入的option是这样的结构
const modulesA = {
namespaced: true,
state: {
conut: 1
},
getters: {
computedConut(state) {
return state.conut + 1
}
},
mutations: {
add(state) {
state.conut + 1
}
},
actions: {
addCount(context) {
context.commit('add')
}
}
}
const store = new Vuex.Store({
modules: {
modulesA
},
state: {
conut: 1
},
getters: {
computedConut(state) {
return state.conut + 1
}
},
mutations: {
add(state) {
state.conut + 1
}
},
actions: {
addCount(context) {
context.commit('add')
}
}
})
在执行ModuleCollection的constructor的时候,参数rawRootModule就是之前的options,constructor会执行this.register,第一参数传入空数组,第二个参数传入rawRootModule,第三个参数传false。在这里作者给出的注释是// register root module (Vuex.Store options)注册根部module。register函数首先会通过const newModule = new Module(rawModule, runtime),rawModule为rawRootModule,runtime为false。Module在执行constructor的时候会执行this._rawModule = rawModule来保存传入的rawModule, 执行const rawState = rawModule.state来保存rawModule为的state,也就是传入的对象根部的state,接着通过this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}来保存state,也就是说根部的state既可以是对象的形式,也可以是一个函数执行后返回对象的形式。这样register函数的newModule就是一个Module实例。接着register函数会判断path的长度,path在首次执行传入的是一个空数组,也就是会执行this.root = newModule把我们实例化的newModule作为root。接着会判断rawModule.modules在上边的demo中,是有编写modules的,所以会通过Object.keys遍历rawModule.modules,去执行 this.register(path.concat(key), rawChildModule, runtime),path在第一次是一个空数组,但执行到了这一步,会通过concat,把遍历到的modules的key添加到数组中,这样path就成了['modulesA'],然后第二个参数为modulesA的值。这样递归的执行register函数进行注册,和第一次执行register不同的是,会先去执行const parent = this.get(path.slice(0, -1)),也就是执行get方法,由于对path进行了剔除最后一位的操作,所以传入的是一个空数组。get函数会对传入的path做一个reduce,reduce每次执行会把当前的key作为参数传入Module实例的getChild方法,执行并返回结果,Module实例的getChild方法会返回当前this._children[key]。对于本次执行get由于传入的path参数是一个空数组,所以会直接返回this.root,那么parent就是第一次执行register的Module实例。接着本次的register会执行parent.addChild(path[path.length - 1], newModule),第一个参数传入path的最后一位,也就是moduleA字符串,第二个参数传入的是本次register的Module实例,addChild会执行this._children[key] = module这样就把ModuleA创建的Module实例存入了根部Module实例的_children中,那么假如modulesA的modules执行到get方法的时候,就可以拿到modulesA的module实例作为parent,继续执行appendchild操作,这样就递归建立了Module实例之间的父子关系。
// src/module/module-collection.js
class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
...
register (path, rawModule, runtime = true) {
...
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)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
...
}
// src/module/module.js
export 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 = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
...
getChild (key) {
return this._children[key]
}
...
}
三、模块安装
执行new ModuleCollection(options)会为store实例的_modules挂载,之后会执行installModule函数,作者给出的注释是// init root module.,// this also recursively registers all sub-modules以及// and collects all module getters inside this._wrappedGetters,也就是说注册根部模块并且递归的安装所有子模块,并且会把所有模块的getter收集到this._wrappedGetters中。
1、执行makeLocalContext拿到local
执行installModule的时候第一个参数传入当前的store实例,第二个参数传入根部的state,第三个参数传入空数组,第四个参数传入根部的module实例。installModule函数首先执行const isRoot = !path.length,根据传入的path.length(首次是空数组)判断他是不是root,然后执行 const namespace = store._modules.getNamespace(path),getNamespace函数会根据传入的path数组,获取他们的module,如果namespaced为true,那么会返回key+/,假如传入的path为['modulesA','modulesB'],并且两者namespaced都为true,则会返回modulesA/modulesB/,首次执行传入的path为空数组,所以首次namespace为一个空的字符串。接着判断如果第四个参数modulenamespaced为true,那么会执行store._modulesNamespaceMap[namespace] ,也就是说store._modulesNamespaceMap对象中存储的是namespaced为true的module实例,并且以namespace作为key。接着会执行const local = module.context = makeLocalContext(store, namespace, path),makeLocalContext函数整体看下来定义了一个local对象,其中有dispatch和commit,接通过Object.defineProperties为local定义了访问getters和state的返回结果,最终返回local。dispatch和commit都会判断Namespace,如果不是一个空的字符串,也就是说module的namespaced为false,会直接注册,如果有Namespace那么会执行type = namespace + type,也就是说在vuex中注册的带有namesped的module中的mutations和actions最终访问的方式会是modulesA/modulesB/methods,也就是说makeLocalContext函数为我们重写了commit和dispatch。之后定义的getter的访问方式同样也是通过Namespace来判断,如果有,则会执行makeLocalGetters,makeLocalGetters会判断store._makeLocalGettersCache[namespace],也就是是否有了当前module的getter缓存,如果没有则会遍历getters,首先拿到key最后的getter名称,然后通过 Object.defineProperty把当前的getter和全局的getter进行绑定,这样当访问全局的比如modulesA/modulesB/xxxx就会访问到当前module的getter。state的访问方式和之前的不同,他是通过state.ModulesA.ModulesB.xxx进行访问,对应getNestedState函数,去执行return path.reduce((state, key) => state[key], state),这样当访问子模块的state,会一层层去找,最终返回访问module的state。这样在配置store的时候,我们无需去关注他的嵌套层级。
// src/store.js
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
...
store._modulesNamespaceMap[namespace] = module
}
...
const local = module.context = makeLocalContext(store, namespace, path)
...
}
/**
* make localized dispatch, commit, getters and state
* if there is no namespace, just use root ones
*/
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
...
if (!options || !options.root) {
type = namespace + type
...
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
...
if (!options || !options.root) {
type = namespace + type
...
}
store.commit(type, payload, options)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function makeLocalGetters (store, namespace) {
if (!store._makeLocalGettersCache[namespace]) {
const gettersProxy = {}
const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
// skip if the target getter is not match this namespace
if (type.slice(0, splitPos) !== namespace) return
// extract local getter type
const 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.
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
store._makeLocalGettersCache[namespace] = gettersProxy
}
return store._makeLocalGettersCache[namespace]
}