1. Introduction and use of Vuex

1.1 Vuex profile

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.

      

Vuex is mainly composed of state, getter, mutation and Action, forming a single state tree store. For complex businesses, stores can be divided into modules. Each module has its own state, mutation, action, getter, and even nested submodules.

  • State: Data source that drives the application
  • Getter: Computed and processed state, similar to computed in Vue;
  • Mutation: The only way to change the state in Vuex’s store is to commit mutation; Only synchronized methods
  • Action: Action submission mutation, which can contain asynchronous operations

Analysis of Vuex source code in this article is uploaded to github.com/zhengchangs… , the source code comes from Vuex official warehouse.

1.2 Use of Vuex

// store/index
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
import products from './modules/products'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    rootState: 'rootState'
  },
  mutations: {
    rootMutation (state, payload) {
      state.value = payload
    }
  },
  actions: {
    rootAction ({ commit }, payload) {
      commit('updateValue', payload)
    }
  },
  getters: {
    rootGetter: state= > state.rootState
  },
  modules: {
    cart,
    products
  },
})
Copy the code
// app.js
import Vue from 'vue'
import store from './store'

new Vue({
  el: '#app',
  store,
  render: h= > h(App)
})
Copy the code

In the above example, we defined global state, action, mutation, and getter, and introduced two modules: CART and Product. Specific demo please refer to: Demo, this demo is in the official instance (shopping cart) on the basis of the modification, added the global state, for better explanation of the source code.

From the above example, there are three steps to use VUEX:

1. Explicitly install Vuex via vue.use ();

2. Use vuex. Store to construct stores related to actual businesses;

3. Add the Store attribute in the instantiation of Vue;

1.3. How to solve the following problems

After having a good understanding of Vuex, I began my reading of the source code with the following questions.

1. How does Vuex work? How can Vuex get an instance of store from this.$store?

2. What single state tree is built and how to achieve modular management?

3. What is the difference between context and store instances?

4. How to achieve data responsiveness?

5. How to implement functions of auxiliary classes?

2. Registration of Vuex

When Vuex is used, vue.use () must be displayed. In essence, Vuex is actually a plugin of Vue. The Vuex install method is as follows:

let Vue

export function install (_Vue) {
  // If Vuex is already registered, return
  if (Vue && _Vue === Vue) {
    Warning Ignored in // dev environment
    if (__DEV__) {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.')}return
  }
  Vue = _Vue
  applyMixin(Vue)
}
Copy the code

The variable Vue is defined. _Vue is the Vue object that the plug-in registration is actually passed in. First check whether Vue already exists to avoid repeated Vuex registration. Finally call applyMixin method:

//applyMixin.js
export default function (Vue) {
  const version = Number(Vue.version.split('. ') [0])

  // Process Vue 2X and later
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // aop, rewrite Vue instance _init method
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }
  
  function vuexInit () {
    const options = this.$options

    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

First, judge the current Vue version by version. If the Vue version is 2.x or later, mix the relock-beforecreate globally with mixin. For versions 1.x and below, override the _init method. In both cases, the purpose is to call the vuexInit method before the component is initialized. This.$store will be used for the Vue instance when the beforeCreate method is applied for each of the preceding values. For each of the preceding values, the value for this.

Store is the object returned by calling the new vuex.store (options) constructor (Class).

This explains question 1: How does Vuex work? How can a component instance get an instance of a store from this.$store?

When Vuex is registered through vue.use (Vuex), it adds an initialization method to the component. In the initialization method, it adds a store attribute to the instance (this) of the current component and points it to the instance store returned by newvuex.store (). This is the instance store that is returned by newvuex.store () in each component via the this.store attribute. This is why every component can access a Store instance through this.store.

3. Instantiation of vuex. Store

Store is instantiated through new vuex.store (options). The store code is as follows. The store Class is implemented through the Class keyword, and a series of methods are provided.

export class Store {
    
   constructor (options = {}) {}
   
   get state (){}
   
   set state (v){}
    
   commit(){}
   
   dispatch(){}
   
   / /... Other methods
}

Copy the code

Store is the result of the constructor’s execution, with commit and dispatch methods and getter/setter methods for the state property. The details of these methods will be analyzed later.

Let’s start with the key code in the constructor:

"This._research = false this._actions = object.create (null)" This._actionsubscribers = [] // Store action this._mutations = object.create (null) // store mutation, This._wrappedgetters = object.create (null) // Store getter, This. _modules = new ModuleCollection(options) this._modulesNamespaceMap = object. create(null) _subscribers = [] // Mutation this._watcherVM = new Vue() this._makeLocalGettersCache = Object.create(null) // bind commit and dispatch to self const store = this const { dispatch, Commit} = this // dispatch = function boundDispatch (type, payload) {return dispatch. Call (store, type, payload) {return dispatch. Payload)} // Commit This.mit = function boundCommit (type, payload, options) {return commit.call(store, type, // strict mode this.strict = strict //  rootState const state = this._modules.root.state // init root module. // this also recursively registers all Sub-modules // And separate logs all module getters inside this._wrappedgetters // Initialize module, 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

The above code is divided into three parts: 1. Initialize some variables; 2. Execute installModule method. 3. Execute the resetStoreVM method.

This._modules = new ModuleCollection(options)

3.1. ModuleCollection

The purpose of this section of code is to maintain the Module data in the Store as a tree. When executing new ModuleCollection(options), the ModuleCollection constructor is first called as follows:

export default class ModuleCollection {
    constructor (rawRootModule) {
        // Register root module-rawrootModule as options in new vuex.store (options)
        this.register([], rawRootModule, false)}}Copy the code

In the constructor, the regiter method is called to register the top-level module of the store (Vuex initializing options can be read as rootModule). The register method is as follows:

  // The rawModule structure is {state,mutation,action}. For rootModule, the rawModule is the vuex.store instantiation parameter
  
  register (path, rawModule, runtime = true) {
    // The newModule object contains runtime, _children (storing nested modules), _rawModule (current Module), and state (current Module's state) attributes
    const newModule = new Module(rawModule, runtime)
    
    // Is the top-level module (root), if so, the root content points to the current module content.
    if (path.length === 0) {
      this.root = newModule
    } else {
      // path.slice(0, -1) : returns a new array with the last element removed
      // Return the parent module of the current module.
      const parent = this.get(path.slice(0, -1))
      // Add the current module to _children of the parent module. Key is the key of the current module
      parent.addChild(path[path.length - 1], newModule)
    }

    // Handling of nested modules - loop recursive handling
    if (rawModule.modules) {
      // rawChildModule: child module, key: key of the module
      forEachValue(rawModule.modules, (rawChildModule, key) = > {
        // Register submodules
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
Copy the code

The instance returned by new Module() contains four attributes

  • runtime

  • _children: nested child module of the current module

  • _rawModule: current module itself;

  • State: indicates the state data of the current Module

Return to the register method and check whether the current path length is 0. When the register method is called in the constructor, it is passed rootModule and path is an empty array. At this point, set this.root to rootModule; Modules: RawModule. modules: Register (rawmodule. modules) : register (rawmodule. modules) : register (rawmodule. modules) : register (rawmodule. modules)

  • ModuleName path: moduleName for submodules (key moduleName link set registered in modules)

  • RawModule: indicates the current submodule

In the recursive register method, the child Module also returns the same format as rootModule after the new Module function. Since the path passed in is not empty, do the else branch. Look at the this.get method:

 get (path) {
    // From root, layer by layer module calls getChild - get module for key value
    return path.reduce((module, key) = > {
      return module.getChild(key)
    }, this.root)
  }
  
Copy the code

The get method traverses the last Module in the PATH (which is actually the moduleName link collection) link through the array’s Reduce method.

To return to the register method, const parent = this.get(path.slice(0, -1)) The path link set is actually a set of moduleName from root to parent, thus returning the parentModule (parent) of the current module. AddChild (path[path.length-1], newModule) adds the current module to the _children property of parentModule (parent).

After cyclic recursion, the final result of this._module is as follows, forming the number of modules with moduleName as link. Take Demo as an example:

3.2. Initialize installModule

The installModule method mainly completes a series of module initialization, mainly for module state, getter, mutation, action registration. Modules exist in the store, so state, action, etc. in the Module need to be registered. This is achieved by recursively calling the installModule method, where the context property is passed in to the module. The makeLocalContext method is used to access the state and getter of the current Module.

In order to better understand the processing after module setting namespace, we first analyzed the registration of action, mutation and other modules, and then analyzed the implementation of context.

3.2.1. Handle state in this._modules;

The core code for handling state is as follows:

 constisRoot = ! path.lengthif(! isRoot && ! hot) {// Get the state value of the parent module of the current module rootState
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() = > {
      // Reactive handling of state: the constructed state becomes reactive when set by vue. set.
      Vue.set(parentState, moduleName, module.state)
    })
  }
  
  // Other code
  
  module.forEachChild((child, key) = > {
    installModule(store, rootState, path.concat(key), child, hot)
  })

Copy the code

If path is empty, isRoot is true. If statements are skipped. When installModule is recursively called, path is passed in as the moduleName link set for the current Module. The module argument is the current module. GetNestedState is also used to obtain the state at the end of the moduleName link. Similarly, path.slice(0, -1) removes the current moduleName and returns the state (parentState) of the parent Module. Set (parentState, moduleName, module.state) to add moduleName to parentState. Value is the state of the current module. Vue.set is used to realize the responsiveness of data to ensure that the state in the child Module changes and the state in the parentModule changes in response.

Since the module passed in for the first call is store._modules.root, all subsequent recursive operations are based on this entry, which corresponds to state processing, and are ultimately reflected on this._modules.

3.2.2 Mutation: registerMutation

The installModule method iterates through the mutation methods under the current Module and calls the registerMutation method in turn to register. At the same time, the registration of each sub-module is completed by recursion. The core code is as follows:

 // Set of moduleName links for the current module
 const namespace = store._modules.getNamespace(path)

 // mutation traverses the register module - mutation is distinguished by the [nameSpace + key] of the module as the key
  module.forEachMutation((mutation, key) = > {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
  
 / /...
 module.forEachChild((child, key) = > {
    installModule(store, rootState, path.concat(key), child, hot)
  })
 
Copy the code

The code for the registerMutation method is as follows:

// register Mutation - Mutation is maintained on the store._mutation object according to (namespace + handelName)
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    // Injects state under the current module
    handler.call(store, local.state, payload)
  })
}

Copy the code

What is the type parameter? Where namespace is a string formed by the set of moduleName links (for example: ‘CART /’). Module. forEachMutation method traversal of the mutation method, so key is the corresponding method name under mutation. So type for the current module under the link of (moduleName set + method name), for example ‘cart/incrementItemQuantity’. All mutations are stored in a store._mutation object based on the type value. When this handler executes, it injects state into the current Module, where local is the current module context. The final data structure is as follows:

3.2.3 Action registration: registerAction

The final result was stored in the store._action object, which was also a set of key-value pairs. The key value was still (moduleName set + method name), and the value was the method body defined under the corresponding action.

// Register action-action on the store._action object according to type (namespace + handelName -- a/b/).
// The context and root properties are injected at execution time
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    // Injects the context (local) property of the current Module and the root (store) property
    let res = handler.call(store, {
      dispatch: local.dispatch,  // After the assignment, the environment has already published the change when dispatch is executed, so dispatch needs to bind the store via call
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    if(! isPromise(res)) { res =Promise.resolve(res)
    }
      return res
  })
}
Copy the code

More parameters are injected into the Action handler, including context (local) and root (store) attributes. And wrapped through promises, which means that actions triggered by Dispatch can support promise processing.

3.2.4 Register the getter: registerGetter

Gettert does not allow repeat registration. Gettert does not allow repeat registration. All getters are stored in store._wrappedGetters. Getter handlers register state, getter, rootState, and rootGetter.

3.2.5, subtotal

After state, mutation, action, and getter are registered, the final result is as follows:

At this point, question 2 can be answered: What single state tree is built and how is modular management implemented?

The registration of state, mutation, action and getter was realized through recursive operation. Finally, these data were stored in the store instance and managed uniformly by the store according to their properties.

Context construction: makeLocalContext

Vuex provides a context attribute for each module in its design. Context contains four properties: Dispatch, COMMIT, getter, and state.

  • Dispatch and commit: The two methods provided in the context essentially call the corresponding methods in the Store. Taking Dispatch as an example, the core code is as follows:
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) = > {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      // options does not exist or options.root is not true, indicating that the action is obtained from the submodule. Otherwise, operate from root
      if(! options || ! options.root) { type = namespace + typeif(__DEV__ && ! store._actions[type]) {console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return}}return store.dispatch(type, payload)
    }
Copy the code

The context.dispatch method is injected into the first argument of the action. In the action of the module, if the action of the current module needs to be called, it usually says: Dispatch (‘actionName’, payload); However, when an action is registered, all module actions are stored in the store._action object. The corresponding key is concatenated with the moduleName set + method name of each module. If namespace is enabled, you won’t actually find the ‘actionName’ method in the _action object (because the method name in _action is a combination of the moduleName set and the method name).

Dispatch and commit in context are essentially store dispatches and commit methods, but after namespace is enabled, type is processed. A namespace is concatenated to ensure that the corresponding method can be found in the _action object.

  • Getter and state properties: The core code is as follows. Object.defineproperties adds get methods for getters and state, and getters find getters for corresponding Modules through namespaces (essentially moduleName collections). State is to find the state under the corresponding module layer by layer directly through path (which is essentially a set of moduleName).
Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () = > store.getters
        : () = > makeLocalGetters(store, namespace)
    },
    state: {
      get: () = > getNestedState(store.state, path)
    }
Copy the code

The getter for the current module is injected into the action of the module. The getter for the current module is injected into the action of the module. Assuming there is a getter named number, we apply the following to the action:

 const num = getter.num
Copy the code

There are two references involved, the first getter and the second num. The get method in the getter refers to the call to makeLocalGetters as follows:

function makeLocalGetters (store, namespace) {
  if(! store._makeLocalGettersCache[namespace]) {const gettersProxy = {}
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type= > {
      // skip if the target getter is not match this namespace
      // Ignore the namespace mismatch method, namespace: a/b/, getter: A /b/getterName
      if (type.slice(0, splitPos) ! == namespace)return

      // extract local getter type
      / / get getterName
      const localType = type.slice(splitPos)

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () = > store.getters[type],
        enumerable: true
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}
Copy the code

First check if there is a cache. If there is a cache for the current getter, use it directly. If no cache exists, the getter associated with the current namespace is first filtered from store.getter, and then an empty object is defined. DefineProperty is used to set the property-getter method name for the empty Object and the get method for the property.

Back to our example: after two get references, the current getter.name finds the getter method corresponding to moduleName + getterName from the current store.getter property. The Object. DefineProperty method is used to ensure that data is retrieved in real time.

3. What is the difference between context and store instance?

Store contains more properties, whereas context has only four properties commit, Dispatch, state, and getter, where commit and dispatch are essentially store.mit and store.dispatch; State and getter represent the state and getter for the current Module, but only if namespaced properties are enabled on the Module. If namespace is not enabled, the key of moduleName (mutation, action, getter, etc.) is not the moduleName link set + method name, but the method name itself.

4. Data responsive processing: resetStoreVM

In the previous analysis, we used store.state and store.getter for several times, and injected them into properties such as mutation and action. If these properties were not responsive, the function would be completely broken. So how is Vuex implemented? This function is mainly realized through resetStoreVM method. As we all know, Vue is used to deal with data in a responsive manner, and Vuex uses this feature to realize data in a responsive manner. The core code is as follows:

function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  store.getters = {}
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  
  forEachValue(wrappedGetters, (fn, key) = > {
    computed[key] = partial(fn, store)
    // define wrappedGetters properties to store.getter, where the get method is obtained from compute in the vue instance
    // This ensures that when the external getter is obtained, it is actually the property of the vue instance, and the getter's responsive data is realized through the properties of vue
    Object.defineProperty(store.getters, key, {
      get: () = > store._vm[key], // computed
      enumerable: true // for local getters})})// store.vm is defined as an instance of Vue, in which computed attributes are defined as partial(fn, store) === fn(store)
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  
}
Copy the code

According to the above analysis, getters are stored in store._wrappedGetters objects according to the rules of corresponding namespace, and forEachValue method is this time by walking through store._wrappedGetters objects, Copy this object to a computed object and set the get method of store.getters, and the call relationship is as follows:

this. $store. Getter. XXX -- -- -- -- -- - > store. [XXX] (vm1)Copy the code

Further analysis, by calling Vue’s constructor to return the object store. _VM, we know that the data in store. _VM is responsive. Because the computed property of store. _VM is set to a local variable computed object (which is actually a collection of getters), equation (1) can actually be interpreted as: Getting the getter property of store is getting the properties of a computed object in store. _VM. Because store._vm is a responsive object, the data in the getter is also responsive.

Because the Store instance provides a get method for the state property. The code is as follows:

  get state () {
    return this._vm._data.$$state
  }
Copy the code

State = store._vm._data. State = store._vm._data. State = store. Store. _vm is an instance of Vue, so store._vm._data.state. Store. vm is an instance of Vue. Therefore, store.vm.data.state is responsive data, and store.state is also responsive.

This answers the question 4. How to implement data responsiveness?

By constructing a Vue instance, setting the store.state property to data data and the store.getter collection to computed, And point a computed method (a concrete getter) to the corresponding getter method in the store.

5. Store method calls

5.1. Commit method

The commit method is easy to execute, with the mutation part known, and all mutations are stored on the Store. _mutations object. It would be nice if we could find the corresponding mutation execution, but we also need to complete the execution of store-related subscriptions.


   const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    
  const entry = this._mutations[type]
  
  // Execute the mutation method
  entry.forEach(function commitIterator (handler) {
    handler(payload)
  })
  
  this._subscribers.slice().forEach(sub= > sub(mutation, this.state))
  
Copy the code

Where, unifyObjectStyle is to make mutation and action call methods, and finally in the execution is to present a unified data structure.

Store.mit ('increment', {amount: 10}) store.mit ({type: 'increment', amount: 10})Copy the code

5.2. Dispatch method

The dispatch method is stored in the Store._action object. Mutation only needs to find the corresponding action execution and also complete the action-dependent subscription _actionSubscribers (before subscription and After subscription). After actionSubscribers is not executed until all actions are completed by promise. all. The core code is as follows:

this._actionSubscribers
     .slice() 
     .filter(sub= > sub.before)
     .forEach(sub= > sub.before(action, this.state))
     
 const result = entry.length > 1
      ? Promise.all(entry.map(handler= > handler(payload)))
      : entry[0](payload)
      
 return new Promise((resolve, reject) = > {
      result.then(res= > {
        try {
         // Execute After actionSubscribers
        resolve(res)
      }, error= > {
        // Execute actionSubscribers Error
        reject(error)
      })
    })     
Copy the code

6. Auxiliary methods

6.1 mapXXX method

Vuex provides four auxiliary methods mapState, mapGetter, mapMutation and mapAction. The implementation principle is basically the same. Taking mapAction as an example, there are usually two reference methods: object and array:


    // (1) Array format - No namespace. mapActions(['increment'.'incrementBy'
    ]),
    
    // (2) Object form - No namespace. mapActions({add: 'increment'
    })
    
    
    // (3) Array form - has namespace. mapActions('moduleName/moduleName'['increment'.'incrementBy'
    ]),
    
    //(4) Object form - No namespace. mapActions('moduleName/moduleName', {add: 'increment' 
    })

Copy the code

MapActions = mapActions = mapActions = mapActions = mapActions

export const mapActions = normalizeNamespace((namespace, actions) = > {
  const res = {}
  if(__DEV__ && ! isValidMap(actions)) {console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(actions).forEach(({ key, val }) = > {
    res[key] = function mappedAction (. args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch
      // If a namespace exists, set dispatch to the dispatch in module; otherwise, set dispatch to the dispatch in root
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      // When actually called, the action action is triggered by dispatch.
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
Copy the code

NormalizeNamespace is used to unify namespace parameters. If no namespace parameter exists, the parameter is null. For example, (1) in this example is used.

  // (1) Array format - No namespace. mapActions(' '['increment'.'incrementBy' 
    ]),
Copy the code

After normalizeNamespace smoothen out the namespace argument, we are left with the array and object references. As you might expect, normalizeMap is intended to solve the problem of data format incompatibility between the two calls. For example (3), (4), [{key:”,val:”}] :

// (3) Array form - has namespace. mapActions('moduleName/moduleName'[{key:'increment' : val:'increment' }, 
      {key:'incrementBy' : val:'incrementBy'},]),//(4) Object form - No namespace. mapActions('moduleName/moduleName'[{key:add: val:'increment'}])Copy the code

MapXXX will eventually return array-object, the key of which is the key you referenced, and value of which is function. In the case of Dispatch, which is eventually mixed into methods, when the call is actually executed, it actually executes:

 // When actually called, the action action is triggered by dispatch.
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))  / / the commonly used
    }
Copy the code

The corresponding action is executed by calling Dispatch, where type is val, increment method corresponding to instance (4), args is the parameter passed by the execution method, and finally the action is passed in as payload.

Dispatch is the method provided by the current context if a namespace exists. So the following statement is ultimately executed in example (4)

  context.dispatch('increment',payload)
Copy the code

Context. dispatch will add the moduleName link set corresponding to the current namespace, so the above statement will execute:

  store.dispatch('moduleName/moduleName/increment',payload)
Copy the code

6.1 createNamespacedHelpers

The purpose of createNamespacedHelpers is to create helper functions through namespaces. The source code is as follows, very simple, in fact, by passing namespace parameters to mapXXX function.

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

This is the answer to question 5. How are functions of helper classes implemented?

First, unify the way mapXXX method is called: Is there a namespace and object or array invocation method? Then obtain the corresponding attribute elements (state-store. state, action-store._action) in the store object through the corresponding namespace. And returns the key-value object (key is the imported object property or array element, value is the corresponding method or property in the corresponding store), which is injected into the Vue instance in use.

7,

Vuex is essentially a plugin for Vue. Use (Vuex) is referenced and store(this.store) is injected into the Vue instance. This makes it possible to pass the this.store(this.store) property in any Vue instance, This makes the Vuex instance (Store) accessible in all Vue instances through this.store.

Vuex.Store is a class that instantiates each module (root itself can also be understood as module: RootModule) state, getter, mutation, and Action are grouped according to a namespace (if namespace is not set, Therefore, the combination of each namespace can be understood as empty) stored in the corresponding attribute under Store. Meanwhile, the state and getter of Vuex can also realize the data responsiveness through clever use of the data responsiveness characteristics of Vue. A Dispatch method is provided to invoke action and a COMMIT method is provided to invoke mutation.

To facilitate the use of Vuex in Vue, a series of helper functions are provided. The essence of these helper functions is to find the object with the corresponding attribute in store (for example, mapState corresponds to store.state, Store._mutation), which either executes the corresponding method (mapGetter, mapMutation, mapAction) or returns the corresponding attribute (mapState) when needed.