VueX实现原理之installModule原理和作用

295 阅读3分钟

上一章 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 0JaMkQ.png

最后就是递归执行其子级的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
  });