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 introduces the realization of Mutations and Actions in Vuex, which mainly involves the following points:

  • Bind the mutation method defined in the Options option to the Mutations object of the Store instance;
  • Create and implement a COMMIT method (synchronization);
  • Bind the action method defined in the Options option to the Actions object of the Store instance.
  • Create and implement a Dispatch method (asynchronous);

At this point, a simple version of the Vuex state management plug-in is complete; (Modules, namespaces, plug-ins and other advanced capabilities and extension capabilities are not yet supported)

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


Second, Vuex module concept

The realization of state, getters, mutations and Actions in VUEX is introduced respectively.

When the project is large and complex, state, getters, mutations, actions will be particularly chaotic and even difficult to maintain;

At this point, we want to be able to split them into separate modules (i.e. scopes); Each module is maintained for a separate file;

Under each independent module, all the states of the current module are included: State, getters, mutations, actions;

To define modules, you need to use the modules attribute provided by Vuex;

When Vuex is initialized, modules are loaded, and multiple modules declared in modules are merged with the root module.

Vuex’s modules are theoretically modules trees that support infinite levels of recursion.

Remark:

  • If the same name exists in multiple modules, the status changes simultaneously by default. You can add a Namespaced namespace for isolation;
  • Modules with the same name are overwritten during the module collection phase;

Third, the use of Vuex module

1. Create Demo

To fully test vuex’s modules and Namespaced namespace functionality based on the previous example code design;

Create two similar modules: moduleA and moduleB:

ModuleA: moduleA, modify state.name = 20;

// src/store/moduleA

export default {
  state: {
    num: 20
  },
  getters: {},mutations: {
    changeNum(state, payload){ state.num += payload; }},actions: {}};Copy the code

ModuleB: moduleB, modify state.name = 30;

// src/store/moduleB

export default {
  state: {
    num: 30
  },
  getters: {},mutations: {
    changeNum(state, payload){ state.num += payload; }},actions: {}};Copy the code

ModuleA and moduleB are imported and registered with the modules attribute in SRC /store/index.js :(register modules A and B as sub-modules of the index root module)

// src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';  // Use the official vuex plug-in for functional testing

// Introduce two test modules
import moduleA from './moduleA'
import moduleB from './moduleB'

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    / /...
  },
  getters: {
    / /...
  },
  mutations: {
    / /...
  },
  actions: {
    / /...
  },
  // Place bets on book modules A and B in the root module
  modules:{
    moduleA,
    moduleB
  }
});

export default store;
Copy the code

In SRC/app.vue, test module method calls:

Get the status in the module: this.$store.state.moduleA.num;

Note: When operating submodules, add the corresponding module path.

// src/App.vue

<template>
  <div id="app">{{this.$store.state.num}<br>Unit price: 10 yuan<br>Order amount: {{this.$store.getters.getPrice}} yuan<br>
    <button @click="$store.commit('changeNum',5)">Synchronous update: Quantity +5</button>
    <button @click="$store.dispatch('changeNum',-5)">Asynchronous update: Number of asynchronous updates -5</button>
    <! -- Test State data response -->
    <button @click="$store.state.num = 100">Test State data responsiveness</button>

    <br>Module test:<br>ModuleA - number of items: {{this.$store.state.modulea.num}<br>B module - Number of items: {{this.$store.state.moduleb.num}<br>
  </div>
</template>
Copy the code

NPM run serve Starts the service. The test module status can be:

2. Find the problem

Test status updates: Click the Sync Update button

Test results: Three state num of the same name in the three modules (root module, module A, and module B) changed simultaneously and triggered view update.

3. Problem analysis

Superficially, although modules are split through modules, the states between modules are not independent.

Note: Due to the particularity of the demo example, multiple modules have attributes with the same name: Quantity of goods NUM;

When Vuex is initialized, modules are merged, and the same states in multiple modules are merged into an array.

Later, when the state is committed via the synchronous update method $store.mit (‘changeNum’,5), Vuex finds all the changeNum methods and executes them in sequence, which causes the properties of the same name to be updated together;

To make the state between modules independent, that is, to create a separate scope, you need to use the Namespaced namespace to isolate during the state merge phase.

4. Namespace

To strictly partition a space, add a namespaced namespace for your modules:

// src/store/moduleA

export default {
  namespaced: true.// Start the namespace
  state: {
    num: 20
  },
  getters: {},mutations: {
    changeNum(state, payload){ state.num += payload; }},actions: {}};Copy the code

After adding the Namespaced namespace, clicking the update button doesn’t change the state in the module:

Note: In this case, the namespace id of the module needs to be added to trigger the data update of the corresponding module:

<template>
  <div id="app">
    <br>Module test:<br>ModuleA - number of items: {{this.$store.state.modulea.num}<br>B module - Number of items: {{this.$store.state.moduleb.num}<br>
    <button @click="$store.commit('moduleA/changeNum',5)">Module A - Synchronous update: Quantity +5</button>
    <button @click="$store.commit('moduleB/changeNum',5)">B module - Synchronous update: Number +5</button>
  </div>
</template>
Copy the code

Test effect:


Fourth, Vuex module collection implementation

1. Module tree structure – module tree

In the second part of this paper, it is mentioned that the module of Vuex is theoretically a tree structure that supports infinite level recursion.

But the current version of Vuex only deals with a single layer of Options objects;

Therefore, we need to continue to add recursive logic to support deep processing to complete the construction of the “module tree”, that is: implementation of Vuex module collection;

To simulate a multi-level “module tree”, we created a module ModuleC and registered it under ModuleA.

Module level design: Index root module contains module A and module B; Module A also contains module C;

ModuleC: moduleC, modify state.name = 40;

export default {
  namespaced: true.state: {
    num: 40
  },
  getters: {},mutations: {
    changeNum(state, payload){ state.num += payload; }},actions: {}};Copy the code

Introduce module C into module A and register module C as A submodule of module A:

import moduleC from './moduleC'

export default {
  namespaced: true.state: {
    num: 20
  },
  getters: {},mutations: {
    changeNum(state, payload){ state.num += payload; }},actions: {},modules: {
    moduleC
  }
};
Copy the code

2. Logic for module collection

Format the options data, add the hierarchical relationship between father and son modules, and build a “module tree”;

Create SRC /vuex/module/module-collection.js to format options. That is, recursively register submodules with the corresponding parent module to complete the construction of module tree.

Note: Vuex’s module collection process is similar to the AST syntax tree construction process in Vue source code:

  • First, the hierarchy has parent-child relationships and theoretically supports infinite recursion.
  • Second, both use depth-first traversal and use stacks to preserve hierarchies (stacks here act like maps).

When Vuex modules are collected, we expect the following construction results:

// Module tree object{_RAW: 'root module ', _children:{moduleA:{_RAW:Module "A",
      _children:{
        moduleC:{
          _raw:Module "C", _children: {}, state: 'the state of the module C'}}, state: 'the state of the module A'}, moduleB: {_raw:"Module B", _children: {}, state: 'the state of the module B}}, state:' the state of the root module '}Copy the code

3. Implementation of module collection

In the SRC /vuex directory, create the module directory and create the module-collection.js file. Create the ModuleCollection class.

/** * Module collection operation * process the options passed in by the user * register the child module with the corresponding parent module */
class ModuleCollection {
  constructor(options) {
    // ...}}export default ModuleCollection;
Copy the code

The operation of module collection is to recursively process modules in the options option into a tree structure:

Create register method: carry the module path, register the current module, execute the “module tree” object construction logic;

class ModuleCollection {
  constructor(options) {
    // From the root module, register the child module with the parent module
    // Array of arguments 1: stack structure, used to store paths, identifying the hierarchy of the module tree
    this.register([], options);
  }
  /** * Register the child module with the parent module *@param {*} Path Array type, the full path of the module to be registered *@param {*} RootModule Current module object */ to be registered
  register(path, rootModule) {
    // Format and register the current module with the corresponding parent module}}export default ModuleCollection;
Copy the code

1. Process the root module

Build from the root: Format the root module and initialize the newModule “module tree” object:

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rootModule) {
    // Format: Build the Module object
    let newModule = {
      _raw: rootModule,        // The complete object of the current module
      _children: {},           // Submodules of the current module
      state: rootModule.state  // Status of the current module
    }

    // Root module: create the root object of the module tree
    if (path.length == 0) {
      this.root = newModule;
    } else {
      // For non-root modules: register the current module with its parent module}}}export default ModuleCollection;
Copy the code

If the current module has modules submodules, recursively call register (depth-first) to continue registering submodules:

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rootModule) {
    let newModule = {
      _raw: rootModule,
      _children: {},
      state: rootModule.state
    }

    if (path.length == 0) {
      this.root = newModule;
    } else {
      // For non-root modules: register the current module with its parent module
    }

    // If the current module has submodules, continue to register submodules
    if (rootModule.modules) {
      // Use deep recursion to process submodules
      Object.keys(rootModule.modules).forEach(moduleName= > {
        let module = rootModule.modules[moduleName];
        // Register the submodule with the corresponding parent module
        // 1,path: indicates the complete path of the child module to be registered. The current parent module path is combined with the child moduleName moduleName
        // 2,module: submodule object to be registered
        this.register(path.concat(moduleName), module)}); }}}export default ModuleCollection;
Copy the code

Note that when register is called, the path hierarchy of the current submodule needs to be spliced together so that the parent module can be found quickly when the hierarchy relationship is determined.

2. Deal with submodules

When the register method processes non-root modules, the current module needs to be registered with the corresponding parent module.

To do this, we need to find the parent module object of the current module layer by layer from the root module tree object and add the child module to it:

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rootModule) {
    let newModule = {
      _raw: rootModule,
      _children: {},
      state: rootModule.state
    }

    if (path.length == 0) {
      this.root = newModule;
    // For non-root modules: register the current module with its parent module
    } else {
      // Find the parent of the current module layer by layer (e.g. path = [a,b,c,d])
      let parent = path.slice(0, -1).reduce((memo, current) = > {
        // Find module A from the root module; Find module B from module A; Find module C from module B; End Module C is the parent of module D
        return memo._children[current];
      }, this.root)
      // Register module D with module C
      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

3. Sub-module registration logic

Assume: module level depth is [A, B, C, D];

Q: How do I register module D with its parent module C?

  • According to the module path carried by module D in the register method, i.epath = [a, b, c, d];
  • throughpath.slice(0, -1).reduceFind the parent module of module D, namely module C, layer by layer from the tree object of the currently completed root module.
  • After formatting the submodule D, it registers with the parent module C, thus completing the Vuex module collection operation.

Test module tree construction results:

In the root module object, there are two submodules: module A and module B.

Wherein, module A contains A sub-module: module C;

The building result is consistent with the expectation, and the module tree is completed.


Five, the end

This article mainly introduces how vuEX module collection is implemented, including the following points:

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

In the next chapter, we continue to introduce Vuex module related concepts: implementation of Vuex module installation;


Maintain a log

  • 20211006
    • Part of the description is adjusted to make the module collection ideas and key logic easy to understand;