Related util tool methods

  1. IsObject method that checks whether the obj argument is an object.
export function isObject (obj) {
  returnobj ! = =null && typeof obj === 'object'
}
Copy the code

InstallModule Parameter Description

  1. Store: indicates the store object instance
  2. RootState: indicates the state of the root module
  3. Path: An array that stores module names in order (module nesting level). The registration module was introduced in the previous chapter.
  4. Module: Module object
  5. Hot: A Boolean value that, along with the internal variable isRoot, controls whether state is set with vue.set
function installModule(store, rootState, path, module, hot) {
    // ...
}
Copy the code

Step by step analysis of installModule functions

Define the variables isRoot and namespace

  • Once called, the installModule first defines two variables isRoot and namespace at the top of the function.
constisRoot = ! path.lengthconst namespace = store._modules.getNamespace(path)
Copy the code
  1. IsRoot is a Boolean value. For the root module (path.length = 0), the value is true. If it is a non-root module (path.length > 0), the value is false.

  2. Namespace is a string. The string returned by calling the module’s getNamespace method and concatenating the module name with a ‘/’. For example, ‘moduleB/’ or ‘moduleB/moduleA/’. As for the explanation of this method, the case has been introduced in the previous chapter.

Register modules in the namespace map

_modulesNamespaceMap is an object defined when the Store instance is initialized and can be understood as a module namespace mapping table. It is specifically used to store modules with namespaces.

// module.namespaced is true, indicating that the module has namespace enabled.
// modules with namespaced: true are defined.
if (module.namespaced) {
    // __DEV__ is true in the development environment
    // Whether the namespace already exists in _modulesNamespaceMap
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
}
Copy the code

The following figure shows the _modulesNamespaceMap object after it has been stored.

Those of you who have perused the vuex documentation should easily understand what _modulesNamespaceMap does. It’s associated with functions like mapState, mapGetters, mapActions, and mapMutations. The source code for these functions uses this object. When you use these functions to bind a module with a namespace, you can pass the module’s space name string as the first argument to the above function to make it easier to write, so that all bindings automatically use the module as a context. The following example is from the vuex official documentation.

computed: { ... mapState('some/nested/module', {
    a: state= > state.a,
    b: state= > state.b
  })
},
methods: {
  ...mapActions('some/nested/module'['foo'.// -> this.foo()
    'bar' // -> this.bar()])}Copy the code

Setting the State object

This state refers to the state of the root module (this._modules.root.state).

To set state, both isRoot and hot must be false (inversely true).

 if(! isRoot && ! hot) {// Get the state of the parent module. RootState is the state of the root module, path.slice(0, -1) returns one
    // Array that does not contain the last element of the PATH array.
    const parentState = getNestedState(rootState, path.slice(0, -1))
    // Submodule 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('. ')}"`)}}// Add a new attribute to the state object of the parent module. This attribute is the name of the child module and the value is the state object of the child module
      Vue.set(parentState, moduleName, module.state)
    })
  }
Copy the code

As you can see, when vue.set is used to add new properties to the state object of the parent module, it is called in the _withCommit method of the Store instance.

_withCommit(fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
}
Copy the code

This function is very simple. This saves the raw value of _research (Boolean (initialization), default false), sets it to true, and then executes the fn function (the function passed in when calling _withCommit, usually changing state). When this function is done, Set _research to the original value.

But what’s the point? Look at the code below.

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

The enableStrictMode method is called whenever strict mode is enabled. This method listens on this._data.$$state (which points to rootState) with $watch in Vue, and once you modify the rootState and store. __research value is false, it will throw an error. Case of initialization, default _research is false.

However, in source code, changing state is unavoidable. So, to prevent errors, put the state change operation in _withCommit (right), because it sets _research to true (right) before changing the state, and then restore _research (right) after the change. So, understand?

MakeLocalContext creates the module context

const local = module.context = makeLocalContext(store, namespace, path)
Copy the code

MakeLocalContext generates the corresponding context for each module.

The Dispatch, COMMIT, getters, and State properties are defined. Here is the source code for the makeLocalContext function.

function makeLocalContext(store, namespace, path) {
  // Determine whether the namespace path exists
  const noNamespace = namespace === ' '
  // Define dispatch, commit, getters, and state in the local object and return local
  const local = {
    // Determine by namespace. If true, call store.dispatch directly, otherwise first
    // Parameters are processed before store.dispatch is called
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) = > {
      // unifyObjectStyle adjusts the three accepted parameters based on the _type parameter type (more on that below)
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options} = args
      let { type } = args
      
      // options.root allows distribution of root actions in namespace modules when true, otherwise only
      // Action in the current module
      
      // if options does not exist (null or undefined) or options.root is false, the namespace path will be changed
      // Concatenate with type (action function name). Concatenated string: 'moduleB/increment', used
      $store. Dispatch ('moduleB/increment')
      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)
    },
    // Same as dispatch
    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 + typeif(__DEV__ && ! store._mutations[type]) {console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }
  
  // Add the getters and state attributes to the local object
  Getter and state objects must be lazily fetched because they will be changed by VM updates
  Object.defineProperties(local, {
    getters: {
      get: noNamespace ?
        () = > store.getters : () = > makeLocalGetters(store, namespace)
    },
    state: {
      get: () = > getNestedState(store.state, path)
    }
  })

  return local
}
Copy the code
  • UnifyObjectStyle method

Before interpreting this approach, there are two things to know:

  1. Actions supports both payload and object distribution
// Distribute in payload form
store.dispatch('incrementAsync', {
  amount: 10
})

// Distribute as objects
store.dispatch({
  type: 'incrementAsync'.amount: 10
})
Copy the code
  1. Dispatch Introduction

The distribution of the action. Options can have root: true, which allows distribution of root actions in the namespace module. Return a Promise that resolves all action handlers that are triggered.

dispatch(type: string, payload? : any, options? :Object) :Promise<any>

dispatch(action: Object, options? :Object) :Promise<any>
Copy the code

Can you see that? From the two points listed above, it is clear that action can be distributed in two forms, and that the first parameter type passed in for distribution is not the same. It can be a String or an Object.

To handle this, the unifyObjectStyle method was created. Look at the code below.

function unifyObjectStyle(type, payload, options) {
  // If type is an object and type.type is true, it is distributed as an object. Parameters need to be adjusted, otherwise it is a payload
  / / distribution. And the adjustment of parameters is based on the form of load.
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }
  // __DEV__ is true in the development environment
  if (__DEV__) {
    assert(typeof type === 'string'.`expects string as the type, but found The ${typeof type}. `)}// Put the argument in an object.
  return {
    type,
    payload,
    options
  }
}
Copy the code
  • MakeLocalGetters method
/ / the cache getters
function makeLocalGetters(store, namespace) {
  // Whether cache exists
  if(! store._makeLocalGettersCache[namespace]) {const gettersProxy = {} // can be interpreted as a proxy object for getters
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type= > {
      // Example (only for example) : type -- > 'moduleB/increment'

      // If the namespace of the target getter does not match the namespace, skip it.
      // Note: this code is used with the registerGetter method to make it easier to understand store.getters
      ModuleB /increment = 'moduleB/increment'
      if (type.slice(0, splitPos) ! == namespace)return

      // Extract the name of the getter function
      const localType = type.slice(splitPos)
      
      // Define attributes for the gettersProxy object
      Object.defineProperty(gettersProxy, localType, {
        get: () = > store.getters[type],
        enumerable: true / / can be enumerated
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}
Copy the code
  • GetNestedState method

Obtain the corresponding state of the module. The core of this method is the use of the array method reduce.

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

Register for mutations, Actions, and getters

The source code for registration module mutations, Actions and getters is not complicated. It is not introduced here, so students should try to debug it by themselves.

Register mutations, actions, and getters in the Module and bind this to the current store object
  module.forEachMutation((mutation, key) = > {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) = > {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) = > {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
Copy the code

Registration submodule

Use the forEachChild method defined in the module to traverse the submodule for registration.

 module.forEachChild((child, key) = > {
    installModule(store, rootState, path.concat(key), child, hot)
  })
Copy the code

conclusion

This article has something to offer, although it doesn’t explain the code in detail. Lol…