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;
- through
ModuleCollection
Class, 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;
- through
register(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.e
Vuex.use(vuex)
, the install method in the Vuex plug-in is executed; - The install method receives external Vue instances and passes
Vue.mixin
Implement global sharing of store instances; - Approved in the project
new 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.
- in
new Vue
At 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;