Use cases of Vuex

const store = createStore({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {
    childModule1: {
      namespaced: true.state: {},
      mutations: {},
      actions: {},
      getters: {},
      modules: {
        grandsonModule: {
          namespaced: true.state: {},
          mutations: {},
          actions: {},
          getters: {},},},},childModule2: {
      namespaced: true.state: {},
      mutations: {},
      actions: {},
      getters: {},},},});Copy the code

When using Vuex, multiple modules are often used. So how does Vuex register and process these modules?

Registration and collection of modules

class Store {
  constructor(options) {
    const store = this;

    // Collection module
    store._modules = newModuleCollection(options); }}Copy the code

ModuleCollection

This class is mainly used to handle nested modules, converting the user’s modules into a tree structure within Vuex

class ModuleCollection {
  constructor(rootModule) {
    this.root = null;
    this.register(rootModule, []);
  }

  register(rawModule, path) {
    const newModule = new Module(rawModule);

    if (path.length == 0) {
      // represents a root module
      this.root = rawModule;
    } else {
      // Get the first few items in the path except for the last item, starting from root to get the child
      // Because the last item represents the current module
      const parent = path.slice(0, -1).reduce((module, cur) = > {
        return module.getChild(cur);
      }, this.root);

      // Mount the current module to the corresponding module in the path path
      parent.addChild(path[path.length - 1], newModule);
    }

    // If the module has submodules, register the submodules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) = > {
        // Register the submodule and concatenate the module's path (the module's key)
        this.register(rawChildModule, path.concat(key)); }); }}}Copy the code

The realization of the Module

class Module {
  constructor(rawModule) {
    this._raw = rawModule;
    this._children = {};
    this.state = rawModule.state;
    this.namespaced = rawModule.namespaced;
  }

  getChild(key) {
    return this._children(key);
  }

  addChild(key, module) {
    this._children[key] = module;
  }

  forEachChild(fn) {
    forEachValue(this._children, fn);
  }

  forEachGetter(fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters.fn); }}forEachMutation(fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn); }}forEachAction(fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn); }}}Copy the code

After processing, the Module will end up with the following data structure

store: { _modules: { root: { _raw: rootModule, state: rootModule.state, _children: { childModule1: { _raw: childModule1, state: childModule1.state, _children: { grandsonModule: { _raw: childModule2, state: grandsonModule.state, _children: {}, }, }, }, childModule2: { _raw: childModule2, state: childModule2.state, _children: {},},},}}}Copy the code

Handle modules’ getters, mutations, actions

Register all module’s getters, mutations, actions on the Store instance

store._wrappedGetters = Object.create(null);
store._mutations = Object.create(null);
store._actions = Object.create(null);

// Define the state
const state = store._modules.root.state; / / state
// 
installModule(store, state, [], store._modules.root);

Copy the code
function getNestedState(state, path){
  return path.reduce((curState, key) = > curState[key], state)
}

function installModule(store, state, path, module){

  constisRoot = ! path.length;if(! isRoot){ }// {double: function(state){return state.count * 2}}
  module.forEachGetter((getter, key) = >{
    store._wrappedGetters[key] = () = > {
      return getter(getNestedState(store.state, path))
    }
  })

  // mutation is publish-subscribe based and may have multiple callback functions
  // {add: [mutation, mutation, ...] }
  module.forEachMutation((mutation, key) = > {
      const entry = store._mutations[key] || (store._mutations[key] = [])
      entry.push((payload) = > {  // store.commit("add", payload)
          mutation.call(store, getNestedState(store.state, path), payload)
      })
  })

  The action returns a promise after the mutation is performed
  module.forEachAction((action, key) = > {
      const entry = store._actions[key] || (store._actions[key] = [])
      entry.push((payload) = > {
          let res = action.call(store, store, payload)
          // Determine if res is a promise
          if(! isPromise(res)) {return Promise.resolve(res)
          }
          return res
      })
  })

  // If there are submodules, install submodules
  module.forEachChild((child, key) = > {
      installModule(store, rootState, path.concat(key), child)
  })

}

Copy the code

Namespace handling

Namespaced indicates whether the namespace is enabled or not

Add a method getNamespaced to the ModuleCollection class to get the namespace

// [a,c] => a/c
getNamespaced(path){
    let module = this.root;
    return path.reduce((nameSpaceStr, key) = >{ 
        module = module.getChild(key);  // Get submodules
        return nameSpaceStr + (module.namespaced ? key + "/" : "")},"")}Copy the code

Add the namespace for getters, mutations, and Actions

const namespaced = store._modules.getNamespaced(path)

// store.getters["some/nested/module/foo"]
module.forEachGetter((getter, key) = > {  
    store._wrappedGetters[namespaced + key] = () = > {
      // ...}})// store.commit("some/nested/module/foo", payload)
module.forEachMutation((mutation, key) = > {
    const entry = store._mutations[namespaced + key] || (store._mutations[namespaced + key] = [])
    // ...
})

// store.dispatch("some/nested/module/foo", payload)
module.forEachAction((action, key) = > {
    const entry = store._actions[namespaced + key] || (store._actions[namespaced + key] = [])
    // ...
})

Copy the code

resetStoreState

Proxy getter, and handles the state response

function resetStoreState(store, state){
  Date = 'XXX' without affecting the response of the data
  store._state = reactive({data: state})

  const wrappedGetters = store._wrappedGetters;
  store.getters = {};
  forEachValue(wrappedGetters, (getter, key) = > {
      Object.defineProperty(store.getters, key, {
          get: getter,
          enumerable: true})})}Copy the code

Turn on strict mode

In options, you can specify whether to start strict mode or not. After this mode is enabled, you can only change the state at commit time. Other changes are illegal and will cause errors.

The basic idea of strict pattern implementation

  • Add a state before mutation, _commiting = true
  • Call mutation => change the status, monitor the status, if the current status changes, _commiting = true, synchronize the change
  • _commiting = false
  • If _commiting = false, the status changes, the modification is illegal
class Store{
  this.strict = options.strict || false;
  this._commiting = false;

  resetStoreState(store, state)

  _withCommit(fn){ // Slice mode programming
      const commiting = this._commiting;
      this._commiting = true;
      // The only valid operation is to change the state in fn
      fn();
      this._commiting = commiting;
  }

  commit = (type, payload) = > {
      const entry = this._mutations[type] || [];
      this._withCommit(() = >{
          entry.forEach(handler= > handler(payload))
      })
  }
  dispatch = (type, payload) = > {
      const entry = this._actions[type] || []
      return Promise.all(entry.map(handler= > handler(payload)))
  }
}

function resetStoreState(store, state){
  if(store.strict){
    enableStrictMode(store)
  }
}

function enableStictMode(store){
  watch(() = >store._state.data, () = >{
    console.assert(store._commiting, "State cannot be modified outside mutation")}, {deep: true.flush: 'sync'})}Copy the code

useStore

export const storeKey = 'store'
import {inject} from 'vue'
 // createApp().use(store, 'my')
class Store{
  install(app, injectKey) { 
      // globally exposes a variable that exposes an instance of store
      app.provide(injectKey || storeKey, this)

      // Vue2.x Vue.prototype.$store = this
      app.config.globalProperties.$store = this; // Add the $store attribute so that you can use $store.state.count directly in the template}}// These apis are already exported from vue
// const store = useStore("my")
export function useStore(injectKey = null) {
    returninject(injectKey ! = =null ? injectKey : storeKey)
}
Copy the code