上一章 VueX实现原理之ModuleCollection原理和作用说到了new Collecction建立了this._modules里面的root对象,这个对象的数据结构方便了installModule函数,这篇文章就该说一下installModule函数了
先看这个函数
function installModule (store, rootState, path, module, hot)
后面如果这个实例module有child属性的话,就会遍历child的每一个key,递归调用是入参path是['父级的路径'].concat(key),module便是对应key的value
module.forEachChild(function (child, key) {
installModule(store, rootState, path.concat(key), child, hot);
});
上面的函数封装过了,等同于下面的
for(let key in Object(module.child)){
let child = module.child[key]
installModule(store, rootState, path.concat(key), child, hot);
}
现在开始看看这个方法做了什么
function installModule (store, rootState, path, module, hot) {
var isRoot = !path.length; // 入参path为[],说明这个是根节点
var namespace = store._modules.getNamespace(path); //获得这个module对应的namespace
if (module.namespaced) { // 如果这个module.name 是true,就放进这个map里面,以后通过namespace就能获得相应的实例module了
store._modulesNamespaceMap[namespace] = module;
}
//下面这一步如果module是根组件,也就是isRoot为真的时候,将不会执行,因为这里是建立this._modules.root这个实例module的state属性的父子关系
if (!isRoot && !hot) {
var parentState = getNestedState(rootState, path.slice(0, -1)); //拿到父级的state
var moduleName = path[path.length - 1]; //拿到本次installModule传入的module的name
store._withCommit(function () { //_withCommit是在函数执行时给他一个flag,防止通过mutation和下面这里Vue.set以外的地方改变state的值
Vue.set(parentState, moduleName, module.state); //把本次installModule的state,以键值对的方式放进父级module的state
});
}
//由于state是对象,是地址赋值,当子级state的key有变化时会直接映射到this.modules.root.state上面,比如
//root['A'] = a; a['B'] = b; b['C'] = c,那么通过root['A']['B']['C'].能够获取到c
//到了这里,this._modules.root.state已经能直接通过key/path获取任何一个子孙module.state
//下面这个local就放会一个对象,里面包含四个重要的属性
//1.commit:namespace==='’的时候,返回store.dispatch,否则,将返回一个封装过的store.dispactch,这个方法的只要作用是,传入的type会自动加上namespace前缀,例如namespace为'child',那儿dispatch('grand')等同于dispatch('child/grand/')
//2.dispatch:dispatch和commit同理
//3.getters:namespace===''时,返回this._modules.root.rawModule.getters,否则返回本次传入的mdule的mdule.rawModule.getters
//4.state:返回本次传入的mdule的mdule.rawModule.state
var local = module.context = makeLocalContext(store, namespace, path);
// 上面返回的local将在下面的三forEachXXX方法中的registerXXX函数中用到
/* 1.registerMutation主要是将所有的普通modules对象里面的mutation取出来,
按键值(值为数组)对的方式放入this.$store._mutations里面,
this.$store._mutations只有一层,没有父子关系,
如果一个key对应多个mutation方法,则这个key对应的数组存在多个方法 */
module.forEachMutation(function (mutation, key) {
var namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local);
});
function registerMutation (store, type, handler, local) {
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler (payload) { //key对应的数组存放相同key的方法
//将local.state作为handler(mutation函数)的第一个参数传入
handler.call(store, local.state, payload);
});
}
/*2.forEachAction主要是将所有的普通modules对象里面的actions取出来,
按键值(值为数组)对的方式放入this.$store._actions里面,
this.$store._actions只有一层,没有父子关系,
如果一个key对应多个action方法,则这个key对应的数组存在多个方法*/
module.forEachAction(function (action, key) {
var type = action.root ? key : namespace + key;
var handler = action.handler || action;
registerAction(store, type, handler, local);
});
function registerAction (store, type, handler, local) {
var entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler (payload) {
var 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对象需要把它转换为promise。否则后面的res.then会报错
res = Promise.resolve(res);
}
return res
});
}
/*3. forEachGetter和上面两方法原理差别不大,
把每个modules里的getters方法按键值对的方法放入this.$store._wrappedGetters对象中
,只是在这个对象里,键值对的值就是函数,不是数组
,也就是说每一个key对应的是一个函数,而不是存放多个同名函数的数组,
这样大概是为了在使用getters[key]的时候能得到唯一值*/
module.forEachGetter(function (getter, key) {
var namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local);
});
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
//如果getters里面存在同名函数,即使在不同的module里面,也会报警告
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] duplicate getter key: " + type));
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter( //rawGetter就是getters里面的函数
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
};
}
}
上面local.getters传入,主要是为了实现在执行actions和getters相关函数,如果在该module的namespace为true的情况下,下图红框中能获得局部的getters
最后就是递归执行其子级的installModule,知道最后没有child或者child没有任何key,则停止递归,installModule完成
module.forEachChild(function (child, key) {
installModule(store, rootState, path.concat(key), child, hot);
});
installModule函数之后,this.$store._actions/_mutations/_wrappedGetters等属性里将存放着相关的方法,
_actions属性将在this.$store.dispatch中被使用,
_mutations将在this.$store.commit中被使用
而_wrappedGetters则是作为computed属性的值, 创建了一个Vue实例挂载到this.$store._vm属性上面,
由于还用了Object.defineProperty劫持了store.getters,获取store.getters[key]实际上是获取了store._vm[key]
(store._vm的key属性是通过执行computed里面对应key的方法,将返回的值value以key:value的方法存放在store._vm上,所以store._vm[key]相当于获取了this.$store._wrappedGetters[key]的结果);
var wrappedGetters = store._wrappedGetters;
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});