6. Api Parsing (2)

In the VUE component, in addition to obtaining corresponding state and getters through this.$store, triggering mutations through commit and triggering actions through dispatch, several auxiliary functions are also exposed to help us better manipulate data in VUEX.

1.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

2.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

3.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

4.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.

5.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

6. 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