The overall concept

Vuex is a state management mode developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application and rules to ensure that the state changes in a predictable way.

import Vue from "vue";
import Vuex from "vuex";
import App from './App.vue'

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0,},mutations: {
    increment(state){ state.count++; ,}}});new Vue({
  el: '#app'.render: h= > h(App)
  store
})
Copy the code

This is a simple example of the use we need at the beginning of the project

  1. The introduction ofvuexAnd then throughVue.use(vuex)Register the plug-in
  2. throughnew Vuex.StoreCreating a Store instance
  3. Store is passed as a configuration for options_initIn the function

Use vue. Use for mounting

Vue. What is the use

Vue. Use is defined in SRC /core/global-api/use.js. It is Vue that mounts the static method of the Vue constructor through initUse(Vue) in the initGlobalAPI function.

The vue. use function is divided into two parts, the first part is to prevent duplicate registration, and the second part is to register plugin.

  • Preventing duplicate registration

    This._installedPlugins is initialized to an empty array if it doesn’t exist. If it does, this is returned without registration.

    • In this case, this refers to the vue constructor, that is, when we use vue.use, we still return the vue constructor, which means vue.use can support chained operations.

    • If we find a plugin that we have already registered, we will not register it again to prevent multiple registrations.

  • To register the plugin

    This (vue constructor); this (vue constructor); this (vue constructor); Pass in the newly generated args as an argument, otherwise plugin will be executed directly if plugin is a function. The pluginpassed in is then pushed to this._installedplugins as a registered cache.

Convert an Array-like object to a real Array.

Vue.use = function (plugin: Function | Object) {
  const installedPlugins =
    this._installedPlugins || (this._installedPlugins = []);
  if (installedPlugins.indexOf(plugin) > -1) {
    return this;
  }

  // additional parameters
  const args = toArray(arguments.1);
  args.unshift(this);
  if (typeof plugin.install === "function") {
    plugin.install.apply(plugin, args);
  } else if (typeof plugin === "function") {
    plugin.apply(null, args);
  }
  installedPlugins.push(plugin);
  return this;
};
Copy the code

Install function

Vuex install is defined in SRC /store.js. First, vuex determines that Vue &&_vue === Vue, _Vue is passed in after use. Install is assigned Vue = _Vue when the install function is called, so that the constructor of Vue can be accessed globally from Vue anywhere. If the value has been assigned, it indicates that vue. use has been registered. If it has been registered, return will be returned. Use (vuex) should be called only once.’ [vuex] already installed. Vuex. Install finally executes applyMixin(Vue).

// src/store.js
export function install(_Vue) {
  if (Vue && _Vue === Vue) {
    if (__DEV__) {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      );
    }
    return;
  }
  Vue = _Vue;
  applyMixin(Vue);
}
Copy the code

ApplyMixin function

The applyMixin function is defined in SRC /mixin.js. He gets the Vue version first, and if the Vue version is less than 2, he passes in the Vue init function as an argument to _init. For versions greater than or equal to 2, the vuexInit function is mixed into the beforeCreate via vue. mixin.

$options: if this.$store does exist, mount options.store to this.$store. $store and mount it to this.$store. That is, the root vue instance will mount this.$store for the first time, and subsequent children will be mounted by constantly accessing the parent’s options.parent.$store.

// src/mixin.js
export default function (Vue) {
  const version = Number(Vue.version.split(".") [0]);

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit });
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init;
    Vue.prototype._init = function (options = {}) {
      options.init = options.init ? [vuexInit].concat(options.init) : vuexInit;
      _init.call(this, options);
    };
  }

  /** * Vuex init hook, injected into each instances init hooks list. */

  function vuexInit() {
    const options = this.$options;
    // store injection
    if (options.store) {
      this.$store =
        typeof options.store === "function" ? options.store() : options.store;
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store; }}}Copy the code

Create a Store instance using new vuex. Store

Class Store instantiation

Store is a class defined under SRC /store.js, and when we perform new, we execute its constructor function.

The constructor function mainly does

  • Initialize a number of instance properties (the most important of which isthis._modules = new ModuleCollection(options))
  • performinstallModulefunction
    • The root is initializedmodule
    • Recursively registers all the childrenmodule
    • Put all themoduleThe getter collection is inthis._wrappedGetters
  • To perform theresetStoreVMFunction that initializes the STORE VM and changes the state and getter to responsive data
// src/store.js
export class Store {
  constructor (options = {}) {...// store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)...// init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state) ... }... }Copy the code

New ModuleCollection (options)

The class ModuleCollection constructor executes this.register([], rawRootModule, false) (rawRootModules is the options passed in).

The register function basically does that

  1. throughnew Module(rawModule, runtime)createModuleThe instance
  2. If you judge incomingrawModuleThere aremodules, then traversemodules, continue calling itself (registerFunction)
  3. throughpathIf so, bind the root attribute of the Module instance to the Module instance. If it is a child Module, fetch the parent Module first and then passaddChildMethods The relationship between father and child was established and tree structure was generatedModule.
// src/module/module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

  get (path) {
    // The first call, since it is an empty array, returns this.root, which is the root Module
    return path.reduce((module, key) = > {
      // module._children[key]
      return module.getChild(key)
    }, this.root)
  }
  ...
  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) { // If the path is empty for the first time, the condition is met
      this.root = newModule // In this case, root is the Module instance created by the root store
    }
    // If you call modules register, the path passed in will not be an empty array, and the current logic will be passed
    else {
      Get slice(0, -1) to exclude the last bit. The first call will pass in an empty array
      const parent = this.get(path.slice(0, -1))
      // Get parent by calling addChild => this._children[key] = module
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) { // execute if the passed structure is judged to have modules
      // Register modules. RawChildModule is value and key is the name of modules' item
      forEachValue(rawModule.modules, (rawChildModule, key) = > {
        // When register is called for modules, the first argument passed in is no longer an empty array. Instead, it concates an array of the current modules key
        // The concat method has no side effects, so it is guaranteed to recursively call the register function
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
  ...
}
Copy the code

New ModuleCollection(Options) helps us generate the module instance and form it

class module

The Module executes constructor during instantiation

  1. throughthis._rawModule = rawModuleSave the store structure passed in
  2. Get the current incomingconstructorAnd save the root state as the instance attribute state
// src/store.js
export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime // false
    // Store some children item
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule // Save the store structure passed in
    const rawState = rawModule.state / / the roots of the state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  ...
}
Copy the code

installModule

After assigning the ModuleCollection instance to this._modules, the next step is to install the Module. InstallModule (this, state, [], this._modules.root) passes in four parameters

  • Second parameterstateforthis._modules.root.state, that is,ModuleCollectionThe instanceroot.state.root.stateIs the state of the root Module instance.
  • The fourth parameterthis._modules.rootIs the root Module instance

The installModule function is divided into three main branches

  1. The current is the root Module
  2. Currently a child Module, and Namespaced is true
  3. Currently a child Module, and namespaced is not true

InstallModule mainly does this

  1. First, two variables are defined
  • The isRoot variable => determines whether the current is the root Module by the length of the path array as the third argument.
  • Namespace variable => Passstore._modules.getNamespace(path)Get the access path of the module corresponding to the path (for example:modulesA/modulesB)
  1. throughmakeLocalContextGetlocal (mutation, action, state, and getter) for the current modulemakeLocalContext(store, namespace, path)
  • parameter
    • The first argument is passed to the Store instance
    • The second parameter is passed in and takennamespace(Access path)
    • The third argument is passed to the path array path
  • perform
    • definelocalobject
    • Declare for the local objectdispatchandcommit. According to incomingnamespaceField that determines the current environment (Module)dispatchandcommitThe full path at call timetype, if you havenamespacethetype = namespace + typeSo that when we write modules with true namespace, we don’t need to write the full path when the current Module calls commit or Dispatch
    • throughObject.definePropertiesDeclare for the local objectgettersandstateAccess mode of
    • returnlocalobject
  1. throughregisterMutation.registerAction.registerGetter.installModuleRegister for the current Module instanceMutation.Action.GetteAnd the sonModule
  • If it isinstallModuleIf the submodule is executed, it will passstore._withCommitTo set the state of the store, form a tree structure
// store, state, [], store._modules.root, true
function installModule(store, rootState, path, module, hot) {
  constisRoot = ! path.length;// The first time path is an empty array, true
  // According to path, get the current full path
  const namespace = store._modules.getNamespace(path);

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(
        `[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join(
          "/"
        )}`
      );
    }
    store._modulesNamespaceMap[namespace] = module;
  }
  // If the current is a submodule
  // set state
  if(! isRoot && ! hot) {// Get the parent state
    const parentState = getNestedState(rootState, path.slice(0, -1));
    // Get the module name
    const moduleName = path[path.length - 1];
    store._withCommit(() = > {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join(
              "."
            )}"`); }}// Set the current state to state
      Vue.set(parentState, moduleName, module.state);
    });
  }
  // get the local of the current module (mutation, action, state, getter)
  const local = (module.context = makeLocalContext(store, namespace, path));
  // Register the corresponding Mutation for the current module
  module.forEachMutation((mutation, key) = > {
    const namespacedType = namespace + key;
    registerMutation(store, namespacedType, mutation, local);
  });
  // Register the corresponding Action for the current module
  module.forEachAction((action, key) = > {
    const type = action.root ? key : namespace + key;
    const handler = action.handler || action;
    registerAction(store, type, handler, local);
  });
  // Register the getter for the current module
  module.forEachGetter((getter, key) = > {
    const namespacedType = namespace + key;
    registerGetter(store, namespacedType, getter, local);
  });
  // Walk through the sub-module to install the module
  module.forEachChild((child, key) = > {
    installModule(store, rootState, path.concat(key), child, hot);
  });
}
/** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */
function makeLocalContext(store, namespace, path) {
  const noNamespace = namespace === "";

  const local = {
    dispatch: noNamespace
      ? store.dispatch
      : (_type, _payload, _options) = > {
          const args = unifyObjectStyle(_type, _payload, _options);
          const { payload, options } = args;
          let { type } = args;

          if(! options || ! options.root) { type = namespace + type;if(__DEV__ && ! store._actions[type]) {console.error(
                `[vuex] unknown local action type: ${args.type}, global type: ${type}`
              );
              return; }}return store.dispatch(type, payload);
        },

    commit: noNamespace
      ? store.commit
      : (_type, _payload, _options) = > {
          const args = unifyObjectStyle(_type, _payload, _options);
          const { payload, options } = args;
          let { type } = args;

          if(! options || ! options.root) { type = namespace + type;if(__DEV__ && ! store._mutations[type]) {console.error(
                `[vuex] unknown local mutation type: ${args.type}, global type: ${type}`
              );
              return; } } store.commit(type, payload, options); }};// getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () = > store.getters
        : () = > makeLocalGetters(store, namespace),
    },
    state: {
      get: () = > getNestedState(store.state, path),
    },
  });

  return local;
}
Copy the code

resetStoreVM

resetStoreVM

  1. Access to the oldstore._vmThrough thestore._wrappedGettersGet all registered getters and define emptycomputedobject
  2. traversestore._wrappedGetters, get the registered key and the corresponding callback function, register on a computed object, and passObject.definePropertyPerform data hijacking and define an instance of storegettersProperty will eventually be accessedstore._vm[key].
  3. For the store. _vm assignmentnew Vue, and define the data in it$$stateThe attribute is state (store.state),computed is defined abovecomputedobject
  4. If I have a definitionoldVmWhile the,nextTick(VUE current queue ends), the old VM instance is destroyed.
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) = > {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure environment.
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () = > store._vm[key],
      enumerable: true // for local getters})})// use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins. Vue.config.silent =true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })

  ...

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() = > {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() = > oldVm.$destroy())
  }
}
Copy the code

The API that the Store instance provides for us to use

State, getters, Commit, Dispatch

So let’s say our structure looks something like this

const modulesA = {
  namespaced: true.state: {
    count: 1,},getters: {
    computedConut(state) {
      return state.count + 1; }},mutations: {
    add(state, num){ state.count += num; }},actions: {
    addCount(context) {
      context.commit("add".2); ,}}};const store = new Vuex.Store({
  modules: {
    modulesA,
  },
});
Copy the code

state

If we want to visit modulesA state attribute in the count, the official document given by the API is to use this. $store. State. ModulesA. Count.

When state is accessed, the state get function of the Store instance will be triggered. It will return this._vm._data.$$state. $$state in data corresponds to the defined state. State is the second argument accepted by resetStoreVM, which is this._modules.root.state. This._modules.root. state originally represents the state of the root module, and does not form a nested structure. The state tree is formed when the submodule executes installModule. Set (parentState, moduleName, module.state), add the module’s key as key and module’s state as value to root.state. In other words, the final $$state: state corresponds to the state of a tree structure, so that when we can get the count in the modulesA module through state.modulesa.count. If we are to direct the state change, such as this. $store. State. MoudlesA. Count = 4, so can’t change the state, because the state of the set function he will not do the corresponding state changes, Instead, a warning is issued in development mode.

export class Store {... get state () {return this._vm._data.$$state
  }

  set state (v) {
    if (__DEV__) {
      assert(false.`use store.replaceState() to explicit replace store state.`)}}... }Copy the code
// src/store.js
function installModule (store, rootState, path, module, hot) {...// set state
  if(! isRoot && ! hot) {// Get the parent state
    const parentState = getNestedState(rootState, path.slice(0, -1))
    // Get the module name
    const moduleName = path[path.length - 1]
    store._withCommit(() = >{...// Set the current state to the parent state
      Vue.set(parentState, moduleName, module.state)
    })
  }
  ...
}
Copy the code

getters

Getters [‘modulesA/computedConut’] if we want to access computedConut in getters of modulesA, the API given by the official documentation is this.$store.getters[‘modulesA/computedConut’].

Getter initialization is also done in the resetStoreVM function, which first defines an empty computed object, then iterates through store._wrappedgetters (registerGetter is executed when installModule is run, Mount our defined getters function in store._wrappedgetters), take the getters function as the value, and the key of the getter function as the key for computed, registered in computed, DefineProperty also defines access to store.getters. XXX through object.defineProperty, which ends up on store._vm[key], which is the vm instance of store. He will treat computed as computed for the VM instance, so that by accessing this.$store.getters. XXX it will be proxied to store._vm[key], the specific getters function that we computed for the Vue instance.

// src/store.js
function resetStoreVM (store, state, hot) {...const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) = > {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure environment.
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () = > store._vm[key],
      enumerable: true // for local getters})})// use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins. store._vm =new Vue({
    data: {
      $$state: state
    },
    computed
  })
  ...
}
Copy the code

commit

For example, if we want to call the add function on modulesA to change our state, we need to implement this. codestore.com MIT (‘modulesA/add’). The commit function in the store first parses the three parameters we pass in through the unifyObjectStyle function, so there are two ways to pass in the document

  • commit(type: string, payload? : any, options? : Object)
  • commit(mutation: Object, options? : Object)

So we end up with three parameters, and the first parameter is Type, which is the name of the mutations that we need to call, and the second parameter is payload, which is the parameters that we need to pass in for the mutations that we’re calling, The third argument is options. (By committing commit in actions, you can pass root:true to allow context to access the root module.) The commit function then gets the corresponding mutations function via this._mutations[type] (passed in the registerMutation function), and wrapped around the _withCommit function, Iterate (because mutations are registered as an array in the registration) and pass in the payload as a parameter. Why wrap with _withCommit, what the _withCommit function does is it first sets global _research to true (right), after executing the function, after setting it to false, in the case of resetStoreVM, If strict is true, the enableStrictMode function is executed. The purpose of enableStrictMode is to listen on this._data. If he judges _research to be false, an error is reported. That is to say, if the state is modified through mutations, there will be no error, but if we arbitrarily modify, an error will be reported. The reason you can call the root commit if root is true is because when you execute the makeLocalContext function in installModule, it defines that the commit of a Namespaced Module takes the third parameter, If root is true in the third parameter (options), then the type passed in when calling commit is the original type, not the type concatenated with the namespace.

// src/store.js
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]

    ...

    this._withCommit(() = > {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    ...
  }
Copy the code
// src/store.js
  _withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
Copy the code
// src/store.js
function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () = > {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)}}, {deep: true.sync: true})}Copy the code

dispatch

If we want to call addCount in modulesA’s actions to submit a mutation function, we need to call this.$store.dispatch(‘modulesA/addCount’).

The dispatch function in a store is similar to the commit function. First, the unifyObjectStyle is used to parse the parameters passed in and get the type and payload parameters. Also go to this._actions[type] and get the function registered in the corresponding actions. Unlike commit, it does not execute the commit directly. Instead, it executes the length of the _actions[type] first. If it is 1, it executes it. Promise.all(entry.map(handler => handler(payload))). This is because actions are registered through the registerAction function. The registerAction function checks whether the actions passed in are a promise or not. If not, it will make it a promise by calling res = promise.resolve (res), The dispatch function eventually returns a Promise in which reslove is executed when the entryPromise’s then is executed. That is, we call dispatch and eventually return a promise that triggers then when all the corresponding actions have been executed. As a matter of fact, Dispatch is an asynchronous function, which can accept some asynchronous methods and finally submit mutation to modify the state. Dispatch greatly helped us standardize the way of submitting mutations.

// src/store.js
 dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    ...
    const result = entry.length > 1
      ? Promise.all(entry.map(handler= > handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) = > {
      result.then(res= >{... resolve(res) },error= >{... reject(error) }) }) }Copy the code
// src/store.js
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let 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.resolve(res)
    }
    if (store._devtoolHook) {
      ...
    } else {
      return res
    }
  })
}
Copy the code

RegisterModule and unregisterModule

Vuex also provides dynamic registration and unregistration of Modules

  • Registration module
 this.$store.registerModule('c', {
      namespaced: true. })this.$store.registerModule('c', {
  namespaced: true. })Copy the code

The registerModule function does a type check on the first parameter path. If it is a string, it calls path = [path] to make it an array. If path is not an array, an error is reported. This is because the next step for registerModule is to register the module through this._modules.register. The path accepted is of type array. Path. length > 0. If the first parameter is passed an empty string or array, register is a root module. The registerModule function does not allow the root module to be registered. We then call this._modules. Register (path, rawModule), where the register method is used to register the Module instance. After this step, the module instance is created, then the module is installed through installModule, and finally the resetStoreVM function is called to re-register store. _VM (data, computed), Finally, the old store. _VM is destroyed with $destroy.

// src/store.js
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0.'cannot register the root module by using registerModule.')}this._modules.register(path, rawModule)
    installModule(this.this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this.this.state)
  }
Copy the code
  • The cancellation of module
this.$store.unregisterModule('c')
this.$store.unregisterModule(['modulesA'.'c'])
Copy the code

The unregisterModule function, like the registerModule function, first checks the type of path passed in. If it is a string, path = [path] is executed. If it is not an array, an error is reported. Get (path.slice(0, -1))). The unregister function of the ModuleCollection instance will first fetch the parent module through the passed paththis.get(path.slice(0, -1)). Const child = parent.getChild(key); const child = parent.getChild(key); ModuleCollection getChild () : getChild () : getChild () : getChild () : getChild () : getChild () : getChild () : getChild () : getChild () : getChild (); If the parent module instance is found by key, parent. RemoveChild (key), delete this._children[key] of the module instance, deletes the corresponding parent-child dependencies by delete. GetNestedState (this.state, path.slice(0, -1))); Then call vue. delete(parentState, path[path.length-1]) to delete the state of the corresponding module in the VM instance. ResetStore resetStore resetStore resetStore resetStore resetStore resetStore resets the store instance’s _actions,_mutations,_wrappedGetters, and _modulesNamespaceMap to empty, and returns the state via the store instance again. Reinstall and re-register vm instances by calling the installModule and resetStoreVM functions.

// src/store.js
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)}this._modules.unregister(path)
    this._withCommit(() = > {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)}...function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}
Copy the code

Auxiliary function

mapState

When called, we can get the corresponding state by passing an array or object into the mapState function.

Parameter is object

 computed: mapState({
    count: state= > state.count,
    countAlias: 'count',})Copy the code

Parameter is array

computed: mapState([
  // Map this.count to store.state.count
  'count'
])
Copy the code

The mapState function is defined in helpers.js in the SRC folder. First, the normalizeNamespace function is wrapped around the normalizeNamespace function. Then execute typeof Namespace! == ‘string’ will execute logical map = namespace; Namespace = “, so that when the function inside mapState is actually executed, parameter passing is handled uniformly. If we pass two arguments, we will determine whether the first namespace we pass ends in /. If not, we will concatenate/for us at the end, and finally pass the namespace and map arguments after processing to the internal function for execution. The function inside mapState first defines an empty res variable and then passes the second argument, States, to the normalizeMap function. The normalizeMap function consolidates the number or object we pass into an array object. In this array object, the value corresponding to the key property is the key (string is the key if it is an array), and the value is the val of the array object. The array object is then iterated over, taking the key of each item as the key of the RES, and the value as the defined mappedState function. Finally return the RES object. So what that means is that when we finally execute the mapState helper function, we end up with a RES, whose key is the key that we passed in, and whose value is mappedState, if we put it in computed, because computed automatically evaluates when we access it, Will execute our value, mappedState function as a getter for computed. $store. State and this.$store. Getters get global state and getters, and determine if namespace is empty. If we pass this field (which represents the state we want to get into modules), it first calls getModuleByNamespace with the store instance, the ‘mapState’ string, and the namespace passed in as arguments. The getModuleByNamespace function accesses store._modulesNamespaceMap[namespace] via the namespace passed in. Store. _modulesNamespaceMap Checks whether the current module has namesapced in the installModule function. If so, the module is registered under Store. _modulesNamespaceMap. That is, eventually getModuleByNamespace will get the corresponding Module based on the namespace. Taking the corresponding module, the mappedState function then gets the corresponding state and getters via module.context. Module. context is registered in installModule with the result of makeLocalContext. Getters [type] is the getter in the final store.getters[type], and state is store._vm.state. If val is a function, execute the function and pass state and getters as two arguments, otherwise return state[val], so if we write value as a function, The first parameter is the state of the module corresponding to the current namespace.

// src/helpers.js
/**
 * Reduce the code which written in Vue.js for getting the state.
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
 * @param {Object}* /
export const mapState = normalizeNamespace((namespace, states) = > {
  const res = {}
  ...
  normalizeMap(states).forEach(({ key, val }) = > {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    ...
  })
  return res
})
...
/**
 * Normalize the map
 * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
 * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
 * @param {Array|Object} map
 * @return {Object}* /
function normalizeMap (map) {
  if(! isValidMap(map)) {return[]}return Array.isArray(map)
    ? map.map(key= > ({ key, val: key }))
    : Object.keys(map).map(key= > ({ key, val: map[key] }))
}
...
/**
 * Search a special module from store by namespace. if module not exist, print error message.
 * @param {Object} store
 * @param {String} helper
 * @param {String} namespace
 * @return {Object}* /
function getModuleByNamespace (store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace]
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}() :${namespace}`)}return module
}

Copy the code

mapGetters

When called, we can get the corresponding Getters by passing an array or object into the mapGetters function. Parameter is object

computed: mapState('modulesA/', {
  computedcountA: 'computedcount'
})
Copy the code

Parameter is array

computed: mapState('modulesA/'['computedcount'])
Copy the code

MapGetters is also defined in a helper file, the same as mapState, which is first wrapped by the normalizeNamespace function (which uniformly processes the parameters passed in, eventually to namespace and map). He then also defines a RES object that gets the stored Getters object and eventually returns it. NormalizeMap is used to uniformly process the incoming map into an array object and then iterate over it. Use key as the key of res and value as the mappedGetter function. When we access getters, we execute the mappedGetter function, which is different from the mappedState function in that it first evaluates the namespace, If so, the getModuleByNamespace function will then determine if the module can be found through the namespace passed in. If there is no module, the function will terminate. If we pass in a namespace but do not find the corresponding Module, the execution of the function ends. Console. error(‘ [vuex] module namespace not found in ${helper}(): ${namespace} ‘) when we encountered such an error during the development process, it means that there may be a problem with the namespace we passed in, and VUex did not find the corresponding modle through this namespace. The mappedGetter function eventually returns this.$store.getters[val], this. DefineProperty, and its getter is () => store._vm[key], that is, computed for the VM instance of the store. Store._wrappedgetters is bound by executing the registerGetter function in installModule.

// src/helpers.js
/**
 * Reduce the code which written in Vue.js for getting the getters
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} getters
 * @return {Object}* /
export const mapGetters = normalizeNamespace((namespace, getters) = > {
  const res = {}
  ...
  normalizeMap(getters).forEach(({ key, val }) = > {
    // The namespace has been mutated by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter () {
      if(namespace && ! getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return}...return this.$store.getters[val]
    }
    ...
  })
  return res
})
Copy the code

mapMutations

When called, we can obtain mutations by passing an array or object into the mapMutations function. Parameter is object

methods: { ... mapMutations('modulesA', {
      add(commit, ... rest) {
        commit('add'. rest) },addCount:'add'})},Copy the code

Parameter is array

methods: { ... mapMutations('modulesA'['add'])},Copy the code

When called, we can call mapMutations to generate the mutationn method of the component. The first parameter is the same as mapState and mapGetters, and it is optional to pass in a namespace. The second parameter can be an array or object. The value of an object can be a string or a function. The mapMutations function also uses normalizeNamespace to process a layer of passed parameters. After processing, an object res is also defined, and the map passed in is processed through the normalizeMap function. After traversing the processed map, the key of mutations is used as the key of RES, and the mappedMutation function is used as value. Finally, RES is returned. This is the same as mapState and mapGetters. Mutations will execute the mappedMutation function when we call the mutations option. The mappedMutation function will first get the commit method of the store instance via =this. upgrad.mit, If namesPOace is passed in, it will also get the corresponding module via getModuleByNamespace and redefine the current commit as module.context.com MIT. That means that if a namespace is not passed in (global mutations), the global commit method will be used, and if a namespace is passed in, the local commit method for the corresponding module will be found. Module.context.com MIT makeLocalContext is defined when executing the installModule function, and it will determine if it currently has a namespace, and if it does, it will redefine it when executing store.mit, The first parameter type passed in is the result of concatenating a namespace. If val is a function, it executes the function and takes the commit (local or global commit) and the remaining parameters as arguments to call the function passed in. That is, if we were writing a function, the first argument to the function would be commit. If the evaluation is not a function, the received COMMIT is executed, and the mutations name passed in as the first argument, along with any other arguments passed in. In this way, we can find the complete function of mutations on the current module through local commit, and finally call it.

// src/helpers.js
/**
 * Reduce the code which written in Vue.js for committing the mutation
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept another params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function.
 * @return {Object}* /
export const mapMutations = normalizeNamespace((namespace, mutations) = > {
  const res = {}
  ...
  normalizeMap(mutations).forEach(({ key, val }) = > {
    res[key] = function mappedMutation (. args) {
      // Get the commit method from store
      let commit = this.$store.commit
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
        if (!module) {
          return
        }
        commit = module.context.commit
      }
      return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})
Copy the code

mapActions

When called, we can get the appropriate Actions by passing an array or object into the mapActions function. Parameter is object

methods: { ... mapActions('modulesA', {
      addCount:'addCount'.async addCount(dispatch) {
        await dispatch('addCount')}})Copy the code

Parameter is array

methods: { ... mapActions('modulesA'['addCount'])},Copy the code

The mapActions function is almost the same as the mapMutations function, except for dispatch instead of COMMIT, which will not be detailed here.

createNamespacedHelpers

For the above 4 methods of component binding, we did save some code. However, if we still face a problem, if our current component needs to operate on the state, Action, mutation, and getter of the same namespace, we need to pass in namespace 4 times. That’s what createNamespacedHelpers is all about. CreateNamespacedHelpers function accepts a namespace is divided into parameters, then exposed the outward an object, this object has four attributes, represent the mapState, mapGetters, mapMutations, mapActions, mapState: Mapstate. bind(null, namespace), which takes advantage of the bind function, takes the namespace as the first parameter, and then when we write the parameter, it is the second parameter, so that the first parameter namespace is defined in advance.

// src/helpers.js
/**
 * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
 * @param {String} namespace
 * @return {Object}* /
export const createNamespacedHelpers = (namespace) = > ({
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})
Copy the code