Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.


One, foreword

The last article mainly introduced the implementation of Vuex module collection, mainly involving the following points:

  • Vuex module concept;
  • Use of Vuex modules and namespaces;
  • Implementation of Vuex module collection – building “module tree”;

This article continues to introduce Vuex module related concepts: implementation of Vuex module installation;


Two, the previous comb

SRC /store/index.js:

  • The root module registers submodules with modules: the example contains two submodules A and B;
  • Module A also contains sub-module C, so as to build A three-layer tree structure;
  • So Vuex’s modules are, in theory, a module tree that supports infinite levels;

The process of dependent collection: it is to format data according to Vuex module relations, which is recursion reflected in the code;

  • throughModuleCollectionClass, recursively format the Vuex module to facilitate subsequent state operations;

Here, you can learn from the composition pattern, which is used to deal with hierarchical nested scenarios of tree structures, such as organizational structures;

  • throughregister(path, rootModule)Register a module:
  • path Array type, the full path of the current module to be registered;
  • rootModule Module object to be registered;

At this point, Vuex has completed the maintenance of hierarchical relationships between modules, so as to recursively build a “module tree” object.

Remark:

  • Modules with the same name will be overwritten in Vuex’s module collection phase;
  • By default, updates are triggered simultaneously when the same state exists in multiple modules$store.commit('changeNum', 5); You can add a Namespaced namespace for isolation;
  • After adding a Namespaced namespace, state operations need to add namespace identifiers, such as$store.commit('moduleA/changeNum',5)

Next, according to the formatted “module tree” object, implement Vuex module installation;


Third, the logic of module installation

Module collection: Format module objects into a “module tree”;

Module installation: recurse “module tree” and define getters, mutations, and actions for all modules into the current store instance;

  • Module installation starts from the root module, recursively processing the formatted “module tree” object;
  • According to the module name, all submodules are defined to the root module, and the status is merged to the root module.

In the Store class, create installModule installModule.

Starting from the root module, put the corresponding getter, mutation, and action into the Store class this._actions, this._mutations, and this._wrappedGetters.

Note: Since the module object is not convenient for the expansion of capabilities, it is considered to reconstruct into a class to encapsulate the operations related to the module and provide external calls;


Fourth, code optimization

Optimization 1: Refactoring module objects into module classes

Create the Module class: SRC/vuex/modules/Module. Js

// src/vuex/modules/module.js

/** * Module Module class, provides Module data structure and related capabilities extension */
class Module {
  constructor(newModule) {
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state
  }
  /** * Get module instance * based on module name@param {*} Key Module name *@returns Module instance */
  getChild(key) {
    return this._children[key];
  }
  /** * Adds submodules * to the current module instance@param {*} Key Module name *@param {*} Module Sub-module instance */
  addChild(key, module) {
    this._children[key] = module
  }

  // Based on the Module class, extend other capabilities for the Module...

  /** * traverse the mutations under the current module, the specific treatment is realized by the external callback *@param {*} Fn returns the mutation and key, and the processing logic is implemented by the caller */
  forEachMutation(fn) {
    if (this._raw.mutations) {
      Object.keys(this._raw.mutations).forEach(key= >fn(this._raw.mutations[key],key)); }}/** * iterates through the actions under the current module, which is handled by the external callback *@param {*} Fn returns the current action and key, and the processing logic is implemented by the caller */
  forEachAction(fn) {
    if (this._raw.actions) {
      Object.keys(this._raw.actions).forEach(key= >fn(this._raw.actions[key],key)); }}/** * iterates through the getters under the current module, handling the external callback *@param {*} Fn returns the current getter and key, and the processing logic is implemented by the caller */
  forEachGetter(fn) {
    if (this._raw.getters) {
      Object.keys(this._raw.getters).forEach(key= >fn(this._raw.getters[key],key)); }}/** * traverses the submodules of the current module, handled by the external callback *@param {*} Fn returns the current submodule and key, and the processing logic is implemented by the caller */
  forEachChild(fn) {
    Object.keys(this._children).forEach(key= >fn(this._children[key],key)); }}export default Module;
Copy the code

Modify the ModuleCollection class to update the Module object to the Module class:

import Module from "./module";

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  
  register(path, rootModule) {
    
    // Format: Build the Module object
    // Generate instances as classes for subsequent extensions
    let newModule = new Module(rootModule);
    // let newModule = {
    // _raw: rootModule, // Full object of the current module
    // _children: {}, // Submodules of the current module
    // state: rootModule.state // Current module status
    // } 

    if (path.length == 0) {
      this.root = newModule;
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) = > {
        // If the memo is Module, use getChild.
        return memo.getChild(current);
        // return memo._children[current];
      }, this.root)
			 // If the memo is Module, use addChild.
      parent.addChild(path[path.length - 1], newModule);
      // parent._children[path[path.length - 1]] = newModule
    }

    if (rootModule.modules) {
      Object.keys(rootModule.modules).forEach(moduleName= > {
        let module = rootModule.modules[moduleName];
        this.register(path.concat(moduleName), module)}); }}}export default ModuleCollection;
Copy the code

Optimization 2: Extract object traversal tool methods

Keys are used for Object traversal operation many times in code, which can be encapsulated as a tool function.

Create SRC /vuex/utils. Js file to store all vuex tool functions:

// src/vuex/utils.js

/** * object traversal, return value, key, specific processing by external implementation *@param {*} Obj The object to traverse *@param {*} Callback to the current index processing, and external implementation */
export const forEachValue = (obj, callback) = >{
  Object.keys(obj).forEach(key= >callback(obj[key],key));
}
Copy the code

Replace object.keys with utility functions:

// src/vuex/module/module-collection.js

import { forEachValue } from ".. /utils";
import Module from "./module";

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rootModule) {
    let newModule = new Module(rootModule);
    if (path.length == 0) {
      this.root = newModule;
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) = > {
        return memo.getChild(current);
      }, this.root)
      parent.addChild(path[path.length - 1], newModule);
    }

    if (rootModule.modules) {
      forEachValue(rootModule.modules,(module,moduleName) = >{
        this.register(path.concat(moduleName),module)})// Object.keys(rootModule.modules).forEach(moduleName => {
      // let module = rootModule.modules[moduleName];
      // this.register(path.concat(moduleName), module)
      // });}}}export default ModuleCollection;
Copy the code
import { forEachValue } from ".. /utils";

class Module {
  constructor(newModule) {
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state
  }
  getChild(key) {
    return this._children[key];
  }
  addChild(key, module) {
    this._children[key] = module
  }
  forEachMutation(fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn)
      // Object.keys(this._raw.mutations).forEach(key=>fn(this._raw.mutations[key],key));}}forEachAction(fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn);
      // Object.keys(this._raw.actions).forEach(key=>fn(this._raw.actions[key],key));}}forEachGetter(fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn);
      // Object.keys(this._raw.getters).forEach(key=>fn(this._raw.getters[key],key));}}forEachChild(fn) {
    forEachValue(this._children, fn);
    // Object.keys(this._children).forEach(key=>fn(this._children[key],key));}}export default Module;
Copy the code

Post-optimization test

Function is normal. The Module object has been reconstructed into Module class, and the traversal processing of the current Module getters, mutations and Actions has been added.


Fifth, the implementation of module installation

In SRC /vuex/store.js, create installModule: for vuex module installation;

// src/vuex/store.js

/** * Install the module *@param {*} Store container *@param {*} RootState Indicates the root status *@param {*} Path All paths *@param {*} Module The formatted module object */
const installModule = (store, rootState, path, module) = > {

  // Traverse actions, mutations, getters in the current module
  // Define them respectively to _actions, _mutations, and _wrappedGetters in the store;

  / / traverse mutation
  module.forEachMutation((mutation, key) = > {
    // Processing becomes an array type: each key may have multiple functions that need to be processed
    store._mutations[key] = (store._mutations[key] || []);
    // add the corresponding treatment function to the array of _mutations corresponding to key
    store._mutations[key].push((payload) = > {
      // perform mutation, passing in the state of the current module
      mutation.call(store, module.state, payload); })})/ / traverse the action
  module.forEachAction((action, key) = > {
    store._actions[key] = (store._actions[key] || []);
    store._actions[key].push((payload) = >{ action.call(store, store, payload); })})/ / traverse the getter
  module.forEachGetter((getter, key) = > {
    // Note that getter rename names are overwritten
    store._wrappedGetters[key] = function () {
      // Execute the corresponding getter method, passing in the state of the current module, and return the result of execution
      return getter(module.state)   
    }
  })
  // Iterate over the son of the current module
  module.forEachChild((child, key) = > {
    // Recursively install/load submodulesinstallModule(store, rootState, path.concat(key), child); })}Copy the code
Relying on the Module processing method provided by the Module class, the action, mutation and getter in all modules are collected deeply and recursively into the corresponding _Actions, _mutations and _wrappedGetters in the store instance.Copy the code

Module installation result test:

// src/vuex/store.js

// Initialize the container
export class Store {
  constructor(options) {
    const state = options.state;
    
    this._actions = {};
    this._mutations = {};
    this._wrappedGetters = {};

    this._modules = new ModuleCollection(options);

    installModule(this, state, [], this._modules.root);
    console.log("Module installation result :_mutations".this._mutations)
    console.log("Module installation result :_actions".this._actions)
    console.log("Module setup result :_wrappedGetters".this._wrappedGetters)
  }
	// ...
}
Copy the code

Print _actions, _mutations, _wrappedGetters

_mutations has 4 altogether: root module, module A, module B and module C; _actions: root module; _wrappedGetters: one root module;Copy the code

Six, process combing

  • When the project references and registers the Vuex plug-in, i.eVuex.use(vuex), the install method in the Vuex plug-in is executed;
  • The install method receives external Vue instances and passesVue.mixinImplement global sharing of store instances;
  • Approved in the projectnew Vuex.Store(options)Configure vuEX and initialize the Store state instance.
  • During the Store instantiation phase, the Options option will be processed, and the Vuex module will be collected and installed.
  • innew VueAt initialization, the Store instance is injected into the root vue instance (at this point the Store instance is globally shared);

Seven, the end

This article mainly introduced the implementation of Vuex module installation, and completed the collection and processing of Action, mutation and getter, mainly involving the following points:

  • Vuex module installation logic;
  • Vuex code optimization;
  • Vuex module installation implementation;
  • Vuex initialization process;

In the next chapter, we will continue to introduce Vuex module related concepts: processing of Vuex state;


Maintain a log

  • 20211006:
    • Re-comb the whole text: add code optimization and process comb part;
    • Add necessary code comments;
    • Add test screenshots;