Vuex源码分析——Modules实现原理

781 阅读5分钟

一、modules使用背景:

在使用vuex过程中,如果数据量很大可以用vuexmodules实现模块化。

modules实现包含:数据注册、模块安装

二、什么是数据注册?

数据注册是将用户传入的options做了一次数据的格式化,转换成模块树状结构,实现父子模块的整合。 比如用户传入的options如下:

export default new Vuex.Store({
  state: { state_country: "中国" },
  getters: {},
  mutations: {},
  actions: {},
  modules: {
    city: {
      state: { state_city: "北京" },
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        district: {
          state: { state_district: "海淀" },
          getters: {},
          mutations: {},
          actions: {}
        }
      }
    }
  }
});

转换好的数据结构如下:

{
  root: {
    _rawModule: {
      state: { state_country: "中国" },
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        city: {
          state: { state_city: "北京" },
          getters: {},
          mutations: {},
          actions: {}
        }
      }
    },
    _children: {
      city: {
        _rawModule: {
          state: { state_city: "北京" },
          getters: {},
          mutations: {},
          actions: {}
        },
        _children: {
           district: {
            _rawModule: {
              state: { state_district: "海淀" },
              getters: {},
              mutations: {},
              actions: {}
            },
            _children: {},
            state: { state_district: "海淀" }
          }
        },
        state: { state_city: "北京" }
      }
    },
    state: { state_country: "中国" }
  }
}

module类的实现:

在源码中每个模块的生成通过一个module类来转换。 module的代码如下:

class Module {
  constructor(rawModule) {
    this._children = Object.create(null);
    this._rawModule = rawModule;
    this.state = rawModule.state;
  }
}
  • _children:用于存放子模块。
  • _rawModule:存储自己模块的伪(未被加工)模块时的内容。
  • state:存储自己模块的数据内容。

当前_children属性是空对象,如何向该对象中添加子模块数据?

Module类是对单个模块的处理,而vuex中支持多模块,如果当前模块有子模块的话需要放到_children中,这时候需要通过ModuleCollection类来处理。

ModuleCollection类的实现:

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rawModule) {
    // 将传入的`options`通过`Module`类生成新的模块
    let newModule = new Module(rawModule);
    if (!this.root) {
      this.root = newModule;
    } else {
      // 将生成的新模块添加到它的父元素上
      // 第三次调用时,path = [ 'city' , 'district' ]
      let parentModule = path.slice(0, -1).reduce((root, moduleName) => {
        return root._children[moduleName];
      }, this.root);
      parentModule._children[path[path.length - 1]] = newModule;
    }
    // 如果子元素还有modules,需要递归注册
    if (rawModule.modules) {
      Object.keys(rawModule.modules).forEach(res => {
        // 第一次调用res === 'city',第二次调用res === 'district'
        this.register(path.concat(res), rawModule.modules[res]);
      });
    }
  }
}

ModuleCollection核心的功能是register函数,将所有的子模块整合成树状结构。

register做了什么?

  1. 将传入的options通过Module类生成新的模块。
  2. 将生成的新模块添加到它的父元素上。
  3. 如果当前的模块有子元素,重新执行register,也就是重复第1,2步骤。

register函数的pathrawModule两个参数的作用?

  1. rawModule:表示当前未加工的模块。
  2. path:表示当前模块和所有父级模块的模块名的集合。

为什么要判断是否是this.root
通过实例化ModuleCollection返回的数据结构,它的根模块数据是包在root对象里的。因为根模块调用register,也就是第一次调用register函数的时候是没有this.root的,类里面没有这个属性,所以一定返回false,于是就将当前模块赋值给this.root,因为this.root已被赋值,所以不会再进入这个判断。

较难理解的代码:

let parentModule = path.slice(0, -1).reduce((root, moduleName) => {
  return root._children[moduleName];
}, this.root);
parentModule._children[path[path.length - 1]] = newModule;

核心思想:将当前模块添加到上级模块的_children里。

三、什么是模块安装?

模块的安装最主要的功能就是将注册完的数据里每个模块的stategettersmutationaction递归安装到Store类上,从而用户可以在组件中可以通过this.$store.xxx来操作数据。

先来看下平时vuex怎么调用这些模块:

//获取state
this.$store.state.state_country
//获取getters
this.$store.getters.getter_country
//设置mutations
this.$store.commit("mutation_country","中国")
//设置actions
this.$store.dispatch("action_country",{})

看下Store类具体做了什么?

class Store {
  constructor(options) {
    // 通过实例化`Vue`对象将`state`数据作为`Vue`的属性实现双向绑定
    this.vm = new Vue({
      data() {
        return {
          temp_data: options.state
        };
      }
    });
    // 将options传入的数据进行格式化,即数据注册
    this.modules = new ModuleCollection(options);
    // 新增了`getters`,`mutations`和`actions`三个属性
    this.getters = {};
    this.mutations = {};
    this.actions = {};
    // 通过`installModule`函数将各子模块的`getters`,`mutations`和`actions`整合到`Store`对应的属性上
    installModule(this, this.state, [], this.modules.root);
  }
  get state() {
    return this.vm.temp_data;
  }
  commit = (mutationName, payload) => {
    this.mutations[mutationName].forEach(fn => fn(mutationName, payload));
  };
  dispatch = (actionName, payload) => {
    this.actions[actionName].forEach(fn => fn(actionName, payload));
  };
}
installModule函数的实现:

installModule函数主要是安装stategettersmutationsactions,如果有子模块的话,重复之前的安装步骤,以下是代码的实现:

//将格式化后的数据递归绑定到getters/mutations/actions
const installModule = (store, rootState, path, modules) => {
  //分别安装state,getters,mutations和actions
  installState(path, rootState, modules);
  installGetters(store, modules);
  installMutations(store, modules);
  installActions(store, modules);
  //如果有子模块的话
  if (Object.keys(modules._children).length > 0) {
    Object.keys(modules._children).forEach(childModuleName => {
      installModule(
        store,
        rootState,
        path.concat(childModuleName),
        modules._children[childModuleName]
      );
    });
  }
};
  • store:表示当前Store类。
  • rootState:表示当前要安装模块的state模块。
  • path:表示当前模块和所有父级模块的模块名的集合。
  • modules:表示当前要安装的模块。

installState实现过程

const installState = (path, rootState, modules) => {
  // 如果当前模块不是父级模块
  if (path.length > 0) {
    // 获取到当前模块的父级模块的state对象
    let parentState = path.slice(0, -1).reduce((root, current) => {
      return root[current];
    }, rootState);
    // 给当前模块的父级模块的state对象中增加当前模块的state属性
    Vue.set(parentState, path[path.length - 1], modules.state);
  }
};

最终store上的state参考如下结构:

{
  state_country: "中国",
  city: {
    state_city: "北京",
    district: {
      state_district: "海淀"
    }
  }
};

installGetters实现过程

const installGetters = (store, modules) => {
  const getters = modules._rawModule.getters;
  if (getters) {
    // 遍历当前模块的getters
    Object.keys(getters).forEach(getterName => {
      // 将`modules`的`getters`属性名遍历绑定到`store`的`getters`上
      Object.defineProperty(store.getters, getterName, {
        get: () => {
          return getters[getterName](store.state);
        }
      });
    });
  }
};

installGetters就是将modulesgetters绑定到store上的getters属性上。通过Object.definePropertymodulesgetters属性名遍历绑定到storegetters上,在组件中获取store.getters上的属性实际上获取的是modules上的属性,相当于通过Object.defineProperty做了数据劫持。

installMutations实现过程

const installMutations = (store, modules) => {
  const mutations = modules._rawModule.mutations;
  if (mutations) {
    // 遍历当前模块的mutations
    Object.keys(mutations).forEach(mutationName => {
      // 将`modules`的`mutations`绑定到`store`上的`mutations`对象上
      let temp = store.mutations[mutationName] || (store.mutations[mutationName] = []);
      temp.push((payload) => {
        mutations[mutationName](modules.state, payload);
      });
    });
  }
};

installMutations就是将modulesmutations绑定到store上的mutations对象上。storemutations是对象,键名是mutations的函数名,键值是数组容器,存放所有同名的mutation函数,mutation是不怕重名的,重名的话将逐个执行。mutation函数需要两个参数,一个是mutationsstate,另一个是用户传入的参数。 最终store上的mutations参考如下结构:

{
  mutation_city: [
    (state, val) => (state.state_city = "北京"),
    (state, val) => (state.state_city = val)
  ],
  mutation_country: [
    (state, val) => (state.state_country = val)
  ]
};