This article is suitable for touch vue3 and vuex4 brothers, in addition to Devtools outside the rest of the code have line by line analysis, novice also understand, if there is something wrong with the analysis, welcome big guys comment, no more talk, directly open. Vuex4.0.2 source code download address

Store

// src/store.js 15-70
export function createStore (options{
  return new Store(options)
}

export class Store {
  constructor (options = {}) {
    if (__DEV__) {If the current environment is a development environment, the code in the code block will be executed
    //if ((process.env.NODE_ENV ! == 'production')) {
      assert(typeof Promise! = ='undefined'.`vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)}const {
      plugins = [],/ / the plugin
      strict = false.// Whether to use strict mode
      devtools
    } = options// The object passed in by the user when createStore
    
    // Store internal state
    this._committing = false // Boolean whether data is modified by mutation, useful when strict mode is enabled
    this._actions = Object.create(null) // {[type:string]: Array
      
       } this stores all the processed actions
      
    this._actionSubscribers = [] // Array
      
        is used when the plugin subscribes to actions, as described in the dispatch Function section
      
    this._mutations = Object.create(null) // {[type:string]: Array
      
       } this stores all mutations that have been processed
      
    this._wrappedGetters = Object.create(null) // {[type:string]: Function} this stores all the processed getters
    this._modules = new ModuleCollection(options) // This will convert the data you pass into a tree structure and store it in the ModuleCollection
    this._modulesNamespaceMap = Object.create(null) // {[name:string]: Module} Namespaced modules are true
    // All here, for mapState, mapMutations, mapGetters and mapActions
    this._subscribers = [] // Array
      
        is used when you subscribe to mutations, as discussed in the commit Function
      
    this._makeLocalGettersCache = Object.create(null)// {[namespace:string]: Object} Getters of modules with namespaces
    this._devtools = devtools
    
    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload{
      return dispatch.call(store, type, payload)// This is bound to store, so we can use destruct assignment
    }
    this.commit = function boundCommit (type, payload, options{
      return commit.call(store, type, payload, options)// This is bound to store, so we can use destruct assignment
    }

    // strict mode
    this.strict = strict // Whether to use strict mode, where deep watch is used, not recommended for production environment, consumption of performance

    const state = this._modules.root.state // The object returned by the state function of the object passed in by the user when createStore

    // init root module.
    // this also recursively registers all sub-modules 
    // and collects all module getters inside this._wrappedGetters 
    // Initialize the root module, which registers all submodules recursively at the same time, and collects getters for all modules to this._wrappedgetters
    installModule(this, state, [], this._modules.root)

    // initialize the store state, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // translation: Initialize store state, taking care of responsiveness, (and register _wrappedGetters as a calculated property)
    // The current version (4.0.2) does not actually calculate properties. See the section on resetStoreState
    resetStoreState(this, state)

    // Apply plugins
    plugins.forEach(plugin= > plugin(this))}// ...
}
Copy the code

First of all, you can see that when we initialize a store, we’re actually creating a new store object that provides all the functionality we need. In our new Store(), if we were in a development environment, we would first determine whether the current environment has promises or not, and promises will be used at dispatch and when we initialize actions. Then check whether the constructor is called by new. If not, an error is reported. Here is the source code for Assert.

// src/util.js 64-66
export function assert (condition, msg{
  if(! condition)throw new Error(`[vuex] ${msg}`)}Copy the code

This._modules = new ModuleCollection(options). This._modules.root points to the root module and recurses to the submodules. The value of the _children attribute for each module is an object that holds its children, forming a DOM-like tree structure. We can use this._modules.root._children.a._children.b to get submodule B of submodule A of root module, but the tree structure is not enough, it is only responsible for storing information, what really works is store, So gather mutations, actions, and getters in the module via installModule, and initialize state and getters via resetStoreState, and we’re done.

new ModuleCollection()

// src/module/module-collection.js 1-8 28-48
import Module from './module'
import { assert, forEachValue } from '.. /util'

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    Register the root module (rawRootModule is the object you pass to createStore)
    this.register([], rawRootModule, false)
  }
  
  register (path/ * * / path, rawModule/* Module */ passed by the user, runtime = true/* Whether to run */) {
  // If the runtime is true, registerModule is added dynamically. False is added when registerModule is initialized.
  // Runtime is only useful with unregisterModule
    if (__DEV__) {If the current environment is a development environment, the code in the code block will be executed
//if ((process.env.NODE_ENV ! == 'production')) {
      assertRawModule(path, rawModule)// If actions are not functions, or objects with handler methods,
      // or when getters or mutations are not a function, it returns an error
    }
    
    const newModule = new Module(rawModule, runtime)// Convert the current module object to an instance of module
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }
    
    // register nested modules
    Register nested modules (i.e., recursive register)
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) = > {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
Copy the code

ModuleCollection is the class used to generate the Module tree, which is used only in the new Store(). AssertRawModule, which we’ll talk about later, is called as an instance of the Module. The root module will be mounted on this.root, also known as store._modules.root, and then the child module will be mounted to the parent via a recursive call to the register function, resulting in the tree shown below (what the tree looks like depends on your input).

assertRawModule

AssertRawModule first iterates over assertTypes, verifying mutations, actions, and getters for the module passed in by the user. It’s written this way because it uses policy mode (a design mode, For those interested, check out JavaScript Design Patterns and Development Practices), which makes the code more readable and extensible. If you want to add any new attributes in the future, just add one more attribute to the assertTypes.

// src/module/module-collection.js 112-152
const functionAssert = {
  assertvalue= > typeof value === 'function'.// Type verification function
  expected'function'// The expected incoming value in the error message is later concatenated to the error message
}

const objectAssert = {
  assertvalue= > typeof value === 'function'| | -typeof value === 'object' && typeof value.handler === 'function'),
  expected'function or object with "handler" function'
}

const assertTypes = {
  getters: functionAssert,
  mutations: functionAssert,
  actions: objectAssert
}

// users write mutations, actions, getters error check function
function assertRawModule (path, rawModule{
  Object.keys(assertTypes).forEach(key= > {
    if(! rawModule[key])return
    
    const assertOptions = assertTypes[key]Mutations, actions, getters
    // forEachValue is used to iterate over object attributes
    forEachValue(rawModule[key], (value/* Object value */, type/* The object's key */) = > {
      assert(
        assertOptions.assert(value),// Call the type judge function, false error
        makeAssertionMessage(path, key, type, value, assertOptions.expected)
      )
    })
  })
}

// This function is used to concatenate error messages
function makeAssertionMessage (path, key, type, value, expected{
Mutations (or actions, getters) should be something, but mutations. Add (or something else)
  let buf = `${key} should be ${expected} but "${key}.${type}"`
  if (path.length > 0) {
  // In the xx module
    buf += ` in module "${path.join('. ')}"`
  }
  // It looks like this
  buf += ` is The ${JSON.stringify(value)}. `
  return buf
}

// src/util.js 52-54
// Use it to iterate through the object enumerable property. In traversal to the property)
export function forEachValue (obj, fn{
  Object.keys(obj).forEach(key= > fn(obj[key], key))
}
Copy the code

Module

New ModuleCollection() returns a tree, and new Module() is each node in the tree. It is only responsible for storing the Module objects entered by the user. It is also responsible for maintaining the relationship between modules and providing interfaces for manipulating modules.

import { forEachValue } from '.. /util'

// Base data struct for store's module, package with some attribute and method
The basic data structure of the Store module encapsulates some properties and methods
export default class Module {
// rawModule is the module object you write, runtime indicates whether it is dynamically added
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    // Save some submodules
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    Save the original Module object passed in by the user
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    // Save the state of the original module object
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  get namespaced () {// Only the get method is written here, indicating that this is a read-only property
    return!!!!!this._rawModule.namespaced/ /!!!!! Equivalent to using Boolean() to convert to a Boolean value
  }
  // Add, delete, modify and check the _children object
  addChild (key, module) {
    this._children[key] = module
  }

  removeChild (key) {
    delete this._children[key]
  }

  getChild (key) {
    return this._children[key]
  }

  hasChild (key) {
    return key in this._children
  }
// Hot reload hotUpdate
  update (rawModule) {
  // Since the module.namespaced property is converted to a Boolean value, undefined equals false
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }

  // There are all the methods to traverse the inner attributes
  // Iterate over the child nodes
  forEachChild (fn) {
  // forEachValue is used to iterate over object attributes
    forEachValue(this._children, fn)
  }

  / / traverse getters
  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }
  
  / / traverse the actions
  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  / / traverse mutations
  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

// src/util.js 52-54
// Use it to iterate through the object enumerable property. In traversal to the property)
export function forEachValue (obj, fn{
  Object.keys(obj).forEach(key= > fn(obj[key], key))
}
Copy the code

AddChild, removeChild, getChild, and hasChild all add and delete members of the _children object. On the one hand, they expose the internal interface of the _children attribute, on the other hand, they improve readability. Update method is used for hotUpdate. When using webpack and other tools, hotUpdate can be enabled to improve development efficiency. With the configuration of hot-reload, when you add a new mutations method, the page will not be refreshed, but a new js will be loaded, and the page will be refreshed if it is not worthy.

ModuleCollection

ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) : ModuleCollection (ModuleCollection) Get the module by path, get the namespace, hot update, uninstall the dynamic module, and check whether the module is registered:

// src/module-collection.js 9-27 49-111
export default class ModuleCollection {
  / /...
  For example, if a is a submodule of root and B is a submodule of A, you can use ['a','b'] to obtain module B
  // Equivalent to root._children.a._children.b
  get (path) {
    return path.reduce((module/* The last item evaluates to module */, key/* path Current item, string */) = > {
      return module.getChild(key)/* Returns the submodule, equivalent to module._children[key] */
    }, this.root/* Initial value root module */)}// Concatenate namespace strings by path. If namespaced for both modules A and B are true, the concatenate will be 'a/b/'.
  getNamespace (path/* Array<string> */) {
    let module = this.root
    return path.reduce((namespace, key) = > {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : ' ')},' ')}// Update hotUpdate recursively from the root module down the entire Module tree. Note that new modules cannot be added at this time
  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }
  
  UnregisterModule unregisterModule unregisterModule unregisterModule unregisterModule unregisterModule
  // Only dynamically added modules can be unloaded
  unregister (path/* Array<string> */) {
    // If path is [a,b], path.slice(0, -1) returns a copy with the last item removed.
    // So parent will be the parent module
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]// This will give you the last term, which is 'b'.
    const child = parent.getChild(key)// Get submodules
    
    if(! child) {// If there are no submodules, report an error and return
      if (__DEV__) {// development environment call
        console.warn(
          `[vuex] trying to unregister module '${key}', which is ` +
          `not registered`
          [vuex] tried to uninstall module B, but this module is not registered yet)}return
    }
    // Submodules are not added through registerModule
    if(! child.runtime) {return
    }
    // If the program can get here, there are submodules, and the submodules are added by registerModule
    parent.removeChild(key)
  }

  // If the module is registered, check whether the module is in the tree through the path
  isRegistered (path/* Array<string> */) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    
    if (parent) {
      return parent.hasChild(key)
    }
    
    return false}}// update module tree to hotUpdate hotUpdate
function update (path, targetModule, newModule{
  if (__DEV__) {// The development environment validates user input
    assertRawModule(path, newModule)// I will not repeat it again
  }

  // update target module
  Update the target module
  targetModule.update(newModule)

  // update nested modules
  // Update nested modules
  if (newModule.modules) {
    for (const key in newModule.modules) {
      // Adding new modules during hot updates is not supported
      if(! targetModule.getChild(key)) {if (__DEV__) {
          console.warn(
            `[vuex] trying to add a new module '${key}' on hot reloading, ` +
            'manual reload is needed'
            [vuex] try to add new module XXX in hot update,
            // Manually reloading is recommended)}return
      }
      // Update existing submodules recursively
      update(
        path.concat(key),// Extend path, [a,b] => [a,b,c]
        targetModule.getChild(key),// Old target module
        newModule.modules[key]// New module)}}}Copy the code

installModule

All right, we have formed a tree for saving objects passed in by the user through ModuleCollection, but this tree cannot be used directly. We need to collect mutations, actions and getters in the module through installModule and bind them to the store. This way we can collect the functions that need to be executed for dispatch and COMMIT, and prepare for the next step of resetStoreState. InstallModule is called whenever the module is updated, and resetStoreState is called whenever installModule is called. In other words, the internal state of the Store is updated whenever the object you entered changes.

// src/store-util.js 72-122
// installModule installs the module, which is called whenever the module changes, such as registerModule,
// unregisterModule hotUpdate and new Store()
export function installModule (store, rootState, path, module, hot{
// Store is the store instance. RootState is the state, path, and module of the root module
// Hot is false at initialization, registerModule, we can pass the options parameter preserveState
// to control the value of hot, otherwise true
  constisRoot = ! path.length// If path is [], this is the root module
  // Call ModuleCollection to fetch the namespace
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  // Register the module with the store._modulesNamespaceMap object for mapState, mapMutations, mapGetters, and mapActions
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
    The [vuex] XXX namespace is repeated
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  / / set the state
  if(! isRoot && ! hot) {// Obtain the state of the corresponding parent module from store.state.a.b.c
    const parentState = getNestedState(rootState, path.slice(0, -1))
    / / module name
    const moduleName = path[path.length - 1]
    store._withCommit(() = > {
      if (__DEV__) {// Development environment execution
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('. ')}"`
            The [vuex] parent module's state XXX attribute is overwritten by modules with the same name
          )
        }
      }
      parentState[moduleName] = module.state
    })
  }
  
  // The local is the context object that has the same methods and properties as the store instance when we write actions
  // module.context is used for mapState, mapMutations, mapGetters and mapActions
  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) = > {// Iterate over each mutation of the module
    const namespacedType = namespace + key
    _mutations[namespacedType], which will be called when the mutations are ready for commit
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) = > {// Iterate over each action of the module
    const type = action.root ? key : namespace + key
    // In the assertRawModule section, we stated that actions can be functions or objects with handlers
    const handler = action.handler || action
    // Put the action into the array of store._actions[namespacedType] and wait for dispatch
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) = > {// Iterate over each getter for the module
    const namespacedType = namespace + key
    // Assign state._wrappedgetters [namespacedType] after getter handling, wait for resetStoreState
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) = > {// Iterate over each child of the module
  // Call installModule recursively to traverse the entire tree
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

// src/store-util.js 262-264
// Obtain the state of the corresponding module in the store.state.a.b.c.d format
export function getNestedState (state, path{
  return path.reduce((state, key) = > state[key], state)
}
// src/store.js 259-264
// Called when state is modified to ensure that no errors are reported in strict mode
  _withCommit (fn) {
    // Since _withCommit is a method on the Store prototype, this refers to the Store instance
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
 // src/store-util.js 254-260
 "// With strict mode enabled, deep listen on store.state (case of change) check store. _research value (false) and report an error
 Vuex calls store.prototype._withcommit () for any changes to store
 function enableStrictMode (store{
  watch(() = > store._state.data, () = > {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)}}, {deeptrue.flush'sync'})}Copy the code

To take a look at this code, the first step is to save modules with namespaces tostore._modulesNamespaceMapIn which we will usemapState.mapMutations.mapGettersandmapActions, directly obtain the module through the namespace.

Then performparentState[moduleName] = module.stateThe state of modules other than the root module can be added to the state of the parent module. This is why we can obtain the status of moduleA through store.state.a.

makeLocalContextThis is a function that we’ll talk about in a secondlocalandmodule.contextThis is equivalent to the ‘context object that has the same methods and properties as the store instance’ when we write actions, heremodule.contextAnd also in our usemapGettersThis kind of helper function is useful, as mentioned above, and we can passstore._modulesNamespaceMapDirect access tomoduleAnd thelocalYou can get it just by getting itmodule.contextGet, this is alsomodule.contextThe role of.

The following code loops through the mutations, actions, and getters of the module, essentially reducing the number of parameters of the function by using Currization (a technique that returns a new function via a higher-order function, using closures to bind the original function to this and pass in some parameters in the process). Collect it into the store’s _mutations, _Actions, and _wrappedGetters internal properties. See registerMutation, registerAction, and registerGetter below for code parsing.

Finally, we recurse to make the submodules execute installModule(), so all installModule() does is recursively collect the contents of the module into the store’s internal properties as loaded ammunition.

makeLocalContext

// src/store-util.js 123-179
/** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */
 Create local dispatches, commit, getters, and state * if you don't have a namespace, use the root element */
function makeLocalContext (store/* store instance */, namespace/* Namespace */, path/* Module path */{
  const noNamespace = namespace === ' '// No namespace

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) = > {
      // The unifyObjectStyle method is very common in methods such as dispatch.
      // set the following parameters
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      
      / / this code we can see the document - > https://next.vuex.vuejs.org/zh/api/#dispatch
      // ' 'options' can have' root: true ', which allows distribution of root actions in the namespace module.
      // That is, if options.root is true, type will not be prefixed with namespaces (e.g., 'a/b/'), and therefore dispatch
      // Actions for the root module
      if(! options || ! options.root) { type = namespace + typeif(__DEV__ && ! store._actions[type]) {console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          [vuex] Unknown local action type: A /b/ XXX, global (root module) action type: XXX
          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
      
      / / the same reference document - > https://next.vuex.vuejs.org/zh/api/#commit
      // options can have root: true, which allows the mutation of the root to be submitted in the namespace module.
      if(! options || ! options.root) { type = namespace + typeif(__DEV__ && ! store._mutations[type]) {console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          [vuex] unknown local mutation type A /b/ XXX, global type: XXX
          return
        }
      }
      
      store.commit(type, payload, options)
    }
  }
  // getters and state object must be gotten lazily
  // because they will be changed by state update
  Getters and state objects must be lazy to get
  // Because they will be modified by state update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () = > store.getters
        // makeLocalGetters() returns an object that can be retrieved using the local getter name
        : () = > makeLocalGetters(store, namespace)
    },
    state: {
    // getNestedState, obtain the module state from A.B.C.D
      get() = > getNestedState(store.state, path)
    }
  })

  return local
}

// src/store-util.js 266-279
// The parameter type is passed uniformly
export function unifyObjectStyle (type, payload, options{
  if (isObject(type) && type.type) {// The payload contains the type of the payload
  // The first parameter is payload, and the second parameter is options
    options = payload
    payload = type
    type = type.type
  }

  if (__DEV__) {
    assert(typeof type === 'string'.`expects string as the type, but found The ${typeof type}. `)
    // Translation: Expect type to be string, but you gave type XXX
  }

  return { type, payload, options }
}

// src/util.js 56-58
export function isObject (obj{
  returnobj ! = =null && typeof obj === 'object'
}

// src/store-util.js 180-203
// Create local getters
export function makeLocalGetters (store, namespace{
// Create without cache, return with cache, prevent second calculation
// Getters that can call this function have namespaces, such as 'a/b/xxxGetters'
  if(! store._makeLocalGettersCache[namespace]) {const gettersProxy = {}
    const splitPos = namespace.length
    // All getters are stored in store.getters as key of namespace+type
    Getters ['a/b/xxxGetters']
    Object.keys(store.getters).forEach(type= > {
      // skip if the target getter is not match this namespace
      If the namespace of the target getter does not match, skip it
      // Assume type is 'a/b/xxxGetters', slice is 'a/b/', and returns if it does not match the namespace passed in
      if (type.slice(0, splitPos) ! == namespace)return

      // extract local getter type
      // Extract local getter types
      const localType = type.slice(splitPos)// 'xxxGetters'

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      // Add a port to gettersProxy
      // is defined as a getter property because we don't want to evaluate at this point
      Object.defineProperty(gettersProxy, localType, {
        get() = > store.getters[type],
        enumerabletrue
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}
Copy the code

As you can see from the above code, the object returned by makeLocalContext() is actually helping us to obtain global mutations, actions, and getters on the namespace using local key concatenation, or to obtain local state via A.B.C.D.

Mutations, actions, and getters on the module bind this to the default parameters through coriation, and then collect the code:

registerMutation

// store: store instance, type: namespace key, handler:mutation method, local: local object
function registerMutation (store, type, handler, local{
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // This code is equivalent to:
  // if(! store._mutations[type]){
  // store._mutations[type] = [];
  // }
  // const entry = store._mutations[type]
  entry.push(function wrappedMutationHandler (payload{
    // With currification, we only pass in the payload parameter, and this is bound
    handler.call(store, local.state, payload)
  })
}
Copy the code

registerAction

function registerAction (store, type, handler, local{
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload{
    // Mutation does not return the result directly. Instead, the result is processed and then returned
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // This is a promise because we use promise.all at dispatch
    if(! isPromise(res)) { res =Promise.resolve(res)
    }
    // This is a reject for vue-devtools, which throws a reject error
    if (store._devtoolHook) {
      return res.catch(err= > {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

// src/util.js 60-62
// This is probably for compatibility, as some promises are implemented using polyfill
export function isPromise (val{
  return val && typeof val.then === 'function'
}
Copy the code

registerGetter

function registerGetter (store, type, rawGetter, local{
  // Unlike mutation and action, getters are functions, not arrays of functions
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`)
      [vuex] repeat getter key: XXX
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store{
    return rawGetter(
      local.state, // local state Local state
      local.getters, // local getters local getters
      store.state, // root state State of the root module
      store.getters // root getters and module getters)}}Copy the code

resetStoreState

After completing installModule(), we’ve actually bound the contents of all modules in the module tree to the Store, but we know that when we use getters to get values, we need values, not a function, This means that we need to get the value by calling the getter function we just collected when we get the value (the current version is not a calculated property because caching causes a bug that will be fixed in a later version). State is not currently responsive, so we need to call resetStoreState() for the final step of initialization.

export function resetStoreState (store, state, hot{
// store: store instance, state: store. State, hot whether hotUpdate
  const oldState = store._state

  // bind store public getters
  // Bind store exposed getters
  store.getters = {}
  // reset local getters cache
  // Reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computedObj = {}
  forEachValue(wrappedGetters, (fn, key) = > {
    // use computed to leverage its lazy-caching mechanism
    // Use computed to take advantage of its caching mechanism
    // direct inline function use will lead to closure preserving oldState.
    // using partial to return function with only arguments preserved in closure environment.
    To write fn(store) directly, the closure caches the oldState variable as well
    // A function that returns with partial only stores arguments in its closure (partial implementations are posted below as usual)
    computedObj[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      // TODO: use `computed` when it's possible. at the moment we can't due to
      / / translation:TODO:Use computed tomography when it can be used, but now we can't because of this bug↓
      // https://github.com/vuejs/vuex/pull/1883
      get() = > computedObj[key](),// This is the essence of calling the getter function every time you get a property
      enumerabletrue // for local getters})})// When we access store.state, we are actually accessing store._state.data
  // This is also the key to implementing responsiveness in store.state
  store._state = reactive({
    data: state
  })

  // enable strict mode for new state
  // If you set strict mode, this will start listening deeply for state
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldState) {
    if (hot) {// Whether to use hotUpdate
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      // Translate: Distribute changes among all subscribed observers,
      // Forces getter reevaluation for hot overloading
      store._withCommit(() = > {
        oldState.data = null})}}}// src/util.js 68-73
// The returned function retrieves fn and arg via the closure
// Because there are only two variables in this scope, no extra variables are cached, which improves performance
export function partial (fn, arg{
  return function ({
    return fn(arg)
  }
}
Copy the code

We can see that store.getters binds all the keys on store._wrappedgetters to itself via object.defineProperty, and since we accessed the keys on Store.getters, ComputedObj [key]() is actually called, which is fn(store)(here fn is value on store._wrappedgetters), which is why we can get the calculated values directly using getters. Store. State is reactive because when we access store. State, we actually access store._state.

install

After createStore(), the next step is app.use(Store), which essentially calls the store.prototype.install () method, combined with the useStore() method, We can see that this is essentially a Provide/Inject combination so that we can call useStore anywhere to get a store.

// src/store.js 72-83
export class Store {
  // ...
  install (app, injectKey) {
    / / app is the app, createApp return injectKey type is InjectionKey < Store < any > > | string
    Vue provides an 'InjectionKey' interface, which is a generic type that extends' Symbol '.
    // It can be used to synchronize the type of inject values between producers and consumers
    // https://v3.cn.vuejs.org/api/composition-api.html#provide-inject
    app.provide(injectKey || storeKey, this)// The storeKey is 'store', which is used to implement useStore
    app.config.globalProperties.$store = this// This.$store
    // This is the code that initializes devTool
    const useDevtools = this._devtools ! = =undefined
      ? this._devtools
      : __DEV__ || __VUE_PROD_DEVTOOLS__

    if (useDevtools) {
      addDevtools(app, this)}}}// src/injectKey.js
import { inject } from 'vue'

export const storeKey = 'store'

export function useStore (key = null{
  returninject(key ! = =null ? key : storeKey)
}
Copy the code

state

Reactive is implemented using VUe3’s Reactive.

// src/store.js 85-93
export class Store {
  // ...
  get state () {
    // This._state is the result of reactive(this._modules.root.state)
    // The entire module tree is already mounted on this state
    return this._state.data
  }

  set state (v) {
    if (__DEV__) {
      assert(false.`use store.replaceState() to explicit replace store state.`)
      // translate: Use store.replacEstate () to explicitly replace store.state}}}Copy the code

commit & dispatch

Commit () and Dispatch (), which essentially iterate over the functions collected during installModule(), look a lot like custom events, but with custom events, we only know that the event happened and who subscribed, but the event changes several states. There are several more events that can modify a state, which is completely blind, which is why the GlobalEventBus has been deprecated.

// commit
// src/store.js 95-130
export class Store {
  // ...
  commit (_type, _payload, _options) {
    // check object-style commit
    Check the commit of the object type
    // Write type payload
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)// As mentioned above, I will not repeat it again

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if(! entry) {if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
        [vuex] unknown mutation type: XXX
      }
      return
    }
    this._withCommit(() = > {
      // traverse the store._mutations[type] array, passing parameters to each function
      entry.forEach(function commitIterator (handler{
        handler(payload)
      })
    })
    // Call the plug-in
    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      Shallow copy prevents iterator invalidation if the subscriber calls unSUBSCRIBE synchronously
      .forEach(sub= > sub(mutation, this.state))
      
    Options. silent is no longer supported
    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
        [vuex]mutation type: XXX The Slient option is deprecated.
        // Please use vue-devTools filter Functionality)}}}Copy the code
// dispatch
// src/store.js 132-191
export class Store {
  // ...
  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if(! entry) {if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
        [vuex] Unknown action type: XXX
      }
      return
    }
    
    // Call the plug-in's pre-subscription
    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        Shallow copy prevents iterator invalidation if the subscriber calls unSUBSCRIBE synchronously
        .filter(sub= > sub.before)
        .forEach(sub= > sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        [vuex] the action subscriber failed
        console.error(e)
      }
    }
    // The Promise version of the traversal function array
    const result = entry.length > 1
      ? Promise.all(entry.map(handler= > handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) = > {
      result.then(res= > {
      // Invoke the plug-in's post-subscription
        try {
          this._actionSubscribers
            .filter(sub= > sub.after)
            .forEach(sub= > sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            [vuex] action subscription error
            console.error(e)
          }
        }
        resolve(res)
      }, error= > {
      // Invoke the plug-in's error subscription
        try {
          this._actionSubscribers
            .filter(sub= > sub.error)
            .forEach(sub= > sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
             [vuex]action error subscription error
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }
Copy the code

registerModule

The module is recursively bound to the module tree by calling the ModuleCollection register() method. As soon as the tree changes, the contents of the module tree are synchronized to the Store instance through installModule() and resetStoreState() methods.

// src/store.js 215-227
export class Store {
  // ...
  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.`)
      The module's path must be a string or an array
      assert(path.length > 0.'cannot register the root module by using registerModule.')
      The root module cannot be registered with registerModule
    }
    // Call the ModuleCollection register method to recursively bind modules to the module tree
    this._modules.register(path, rawModule)
    // Bind data on the module to the store
    installModule(this.this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    // Reset store to update getters...
    resetStoreState(this.this.state)
  }
}
Copy the code

unregisterModule

ModuleCollection unregister() is called to unregister the registerModule() registered module. At this time, the store bound internal attributes such as _actions are the contents before uninstallation. Therefore, resetStore() should be used to clear all of them. Then re-collect with installModule(), and reset getters and State with resetStoreState().

// src/store.js 229-242
export class Store {
  // ...
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      The module's path must be a string or an array
    }
    // Call the ModuleCollection unregister method to unload the registered module
    this._modules.unregister(path)
    this._withCommit(() = > {
      // Find the state of the parent module
      const parentState = getNestedState(this.state, path.slice(0, -1))
      // Delete state for submodules
      delete parentState[path[path.length - 1]]
    })
    resetStore(this)}}// src/store-util.js 18-28
export function resetStore (store, hot{
  // Reset some of the internal attributes from the module tree
  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 state
  resetStoreState(store, state, hot)
}
Copy the code

hasModule

Call the isRegistered method of ModuleCollection to get the parent module and then check for the child module on the _children property of the parent module.

// src/store.js 244-252
export class Store {
  // ...
  hasModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      The module's path must be a string or an array
    }

    return this._modules.isRegistered(path)
  }
}
Copy the code

watch

Call vue3’s Watch

// src/store.js 202-207
export class Store {
  // ...
  watch (getter, cb, options) {
    if (__DEV__) {
      assert(typeof getter === 'function'.`store.watch only accepts a function.`)
      Store. Watch only accepts functions
    }
    return watch(() = > getter(this.state, this.getters), cb, Object.assign({}, options))
  }
}
Copy the code

subscribe & subscribeAction

See the official website for details

// src/store.js 193-200
export class Store {
  // ...
  subscribe (fn, options) {
    Subscribers () Add FN to this._subscribers array for COMMIT, return unsubscribe
    return genericSubscribe(fn, this._subscribers, options)
  }

  subscribeAction (fn, options) {
    // {before: fn} indicates that fn will be called before the action is executed
    const subs = typeof fn === 'function' ? { before: fn } : fn
    // Add subs to this._actionSubscribers array for dispatch
    return genericSubscribe(subs, this._actionSubscribers, options)
  }
}

// src/store-util.js 4-16
// Universal subscription
export function genericSubscribe (fn, subs, options{
  if (subs.indexOf(fn) < 0) {
    options && options.prepend
      ? subs.unshift(fn)
      : subs.push(fn)
  }
  // Returns an unsubscribe function
  return () = > {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)}}}Copy the code

hotUpdate

For hot updates, recursively update the module tree, and then reset the Store.

// src/store.js 254-257
export class Store {
  // ...
  hotUpdate (newOptions) {
    // Call the update method on ModuleCollection to update the module tree
    this._modules.update(newOptions)
    resetStore(this.true)}}Copy the code

replaceState

Replace the state

// src/store.js 209-213
export class Store {
  // ...
  replaceState (state) {
    this._withCommit(() = > {
      this._state.data = state
    })
  }
Copy the code

helpers

Before looking at the source code for mapState, mapMutations, mapGetters and mapActions, take a look at the common code for all four functions.

// src/helpers.js 138-194
/** * Return a function expect two param contains namespace and map. it will normalize the namespace and then the Param's function will handle the new namespace and the map. Returns a function that takes the namespace and map parameters, normalizes the namespace, and the function passed in as an argument (fn) processes the new namespace and map@param {Function} fn
 * @return {Function}* /
function normalizeNamespace (fn) {
  return (namespace, map) = > {
    if (typeofnamespace ! = ='string') {// The first argument is map
      map = namespace
      namespace = ' '
    } else if (namespace.charAt(namespace.length - 1)! = ='/') {// Namespace ends with a /
      namespace += '/'
    }
    return fn(namespace, map)
  }
}

/** * Validate whether given map is valid or not *@param {*} map
 * @return {Boolean}* /
function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}


/ * * * the Normalize the standardization of map map (that is, the string [] or {[key: string] : string | Function} into {key: string; value:string|Function}[]) * 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)) {// Not arrays or objects
    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. * 翻译 : Find a specific module in the store by namespace. If the module does not exist, an error is reported. *@param {Object} store
 * @param {String} helper
 * @param {String} namespace
 * @return {Object}* /
function getModuleByNamespace (store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace]// Cached in installModule
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}() :${namespace}`)}return module
}
Copy the code

mapState

By caching the Module context in the Store._ModulesNamespacemap object during installModule(), we can get local state, and the code for the other three methods is pretty much the same.

// src/helpers.js 3-34
/** * Reduce the code written in Vue. Js for getting the state@param {String} [namespace] -module's namespace Specifies the namespace of the Module@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. An Object member can be a function that takes state and getters as arguments, and you can do something with state and getters in this function *@param {Object}* /
// normalizeNamespace Is used to normalize input
export const mapState = normalizeNamespace((namespace, states) = > {
  const res = {}
  if(__DEV__ && ! isValidMap(states)) {An error is reported if the states parameter is not an array or object
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
    [vuex] mapState: The mapper parameter must be an array or object
  }
  // Is used to unify arrays or objects into arrays of objects
  normalizeMap(states).forEach(({ key/* string */, val /* string|Function */}) = > {
    res[key] = function mappedState ({
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        // Get modules from the namespace (cached for installModule)
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        // module.context is also cached with installModule
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
      // a function that takes state and getters as arguments. You can do something with state and getters in this function
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})
Copy the code

mapMutations

By caching the Module context in the store._ModulesNamespacemap object at installModule(), we get a local commit, and the code for the other three methods is pretty much the same.

// src/helpers.js 36-64
** * Reduce the code which written in Vue. Js for research the mutation *@param {String} [namespace] -module's namespace Specifies the namespace of the Module@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, The member of an object can be a function that receives a commit function as its first argument, and it can also receive other parameters. * In this function you can commit mutation and other things. In particular, you need to pass another parameter from the mapping function. *@return {Object}* /
export const mapMutations = normalizeNamespace((namespace, mutations) = > {
  const res = {}
  if(__DEV__ && ! isValidMap(mutations)) {console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object')
    [vuex] mapper: the mapper parameter must be an array or an object
  }
  normalizeMap(mutations).forEach(({ key, val }) = > {
    res[key] = function mappedMutation (. args{
      // Get the commit method from store
      // 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

mapGetters

By caching the Module context in the Store._ModulesNamespacemap object during installModule(), we can get local getters, and the code for the other three methods is pretty much the same.

// src/helpers.js 66-94
/** * Reduce the code 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 = {}
  if(__DEV__ && ! isValidMap(getters)) {console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
    [vuex] mapGetters: Mapper parameters must be arrays or objects
  }
  normalizeMap(getters).forEach(({ key, val }) = > {
    // The namespace has been mutated by normalizeNamespace
    The namespace has been modified by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter ({
      if(namespace && ! getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      if(__DEV__ && ! (valin this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        [vuex] unknown getter: XXX
        return
      }
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})
Copy the code

mapActions

By caching the Module context in the Store._ModulesNamespacemap object during installModule(), we can get local dispatches, and the code for the other three methods is pretty much the same.

// src/helpers.js 96-124
/** * Reduce the code written in vue.js for dispatch the action *@param {String} [namespace] - Module's namespace
 * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, The members of an object can be a function that takes a dispatch function as its first argument, or it can have other parameters. In this function, * you can dispatch the action, or you can do something else. In particular, you need to pass another parameter from the mapping function. *@return {Object}* /
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')
    [vuex] mapActions: The mapper parameter must be an array or object
  }
  normalizeMap(actions).forEach(({ key, val }) = > {
    res[key] = function mappedAction (. args{
      // get dispatch function from store
      // Get the dispatch function from store
      let dispatch = this.$store.dispatch
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})
Copy the code

createNamespacedHelpers

By currization, namespace is preset

// src/helpers.js 126-136
/** * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object * Rebind namespace parameters for mapXXX functions in a special scope and return them via a 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

Deep copy deepCopy

This function caches all nested objects and their copies, considering the loop structure, and if a loop structure is detected, the cached copy is used to avoid an infinite loop. This is a function worth memorizing.

// src/util.js 1-47
/**
 * Get the first item that pass the test
 * by second argument function
 *
 * @param {Array} list
 * @param {Function} f
 * @return {*}* /
export function find (list, f{
  return list.filter(f)[0] // array.prototype. find polyfill
}

/** * Deep copy the given object considering circular structure. * This function caches all nested objects and its Copies. * If it circulates, use cached copy to avoid infinite loops. Considering loop structure, deep copy a given object * this function caches all nested objects and their copies * If loop structure is detected, cache copy is used to avoid infinite loop. * *@param {*} obj
 * @param {Array<Object>} cache
 * @return {*}* /
export function deepCopy (obj, cache = []{
  // just return if obj is immutable value
  Return obj if obj is immutable
  // Primitive types and functions
  if (obj === null || typeofobj ! = ='object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  If a hit is made, it is a loop structure
  const hit = find(cache, c= > c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  Save the copy to the cache first
  // Because we want to reference it in recursive deepCopy
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key= > {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}
Copy the code

plugins

devtool

slightly

logger

Here is the print for default collapse and default expansion:

// src/plugins/logger.js 
// Credits: borrowed code from fcomb/redux-logger

import { deepCopy } from '.. /util'

export function createLogger ({
  collapsed = true.//Filter = (mutation, stateBefore, stateAfter) =>true.//Mutation filter, does not print returnsfalseTransformer = state => state,//MutationTransformer = mut => mut,//Format function actionFilter = (action, state) =>true.//Action filter that does not print returnsfalseActionTransformer = act => act,//Action Formatting function logMutations =true.//Mutations change logActions =true.//Whether to print actions changes logger =console
} = {}{
  return store= > {
    // make a deep copy of store. State for comparison after mutation execution
    let prevState = deepCopy(store.state)

    if (typeof logger === 'undefined') {
      return
    }

    if (logMutations) {// Whether to print mutations
      Subscribe See subscribe & subscribeAction
      store.subscribe((mutation, state) = > {
        // This function is executed just after a mutation has been executed
        const nextState = deepCopy(state)// Current state of deep copy

        if (filter(mutation, prevState, nextState)) {/ / filter mutation
          const formattedTime = getFormattedTime()// Format time, the code is posted below
          const formattedMutation = mutationTransformer(mutation)
          const message = `mutation ${mutation.type}${formattedTime}`

          startMessage(logger, message, collapsed)// Start printing, the code is attached below, the print effect is above
          // Prev state after printing the old state %c is modifiable
          logger.log('%c prev state'.'color: #9E9E9E; font-weight: bold', transformer(prevState))
          The second parameter is the style
          logger.log('%c mutation'.'color: #03A9F4; font-weight: bold', formattedMutation)
          // Prints the new state
          logger.log('%c next state'.'color: #4CAF50; font-weight: bold', transformer(nextState))
          endMessage(logger)
        }
        / / update the prevState
        prevState = nextState
      })
    }
    // the logic is similar to mutation
    if (logActions) {// Whether to print actions changes
      store.subscribeAction((action, state) = > {
        if (actionFilter(action, state)) {
          const formattedTime = getFormattedTime()
          const formattedAction = actionTransformer(action)
          const message = `action ${action.type}${formattedTime}`

          startMessage(logger, message, collapsed)
          logger.log('%c action'.'color: #03A9F4; font-weight: bold', formattedAction)
          endMessage(logger)
        }
      })
    }
  }
}

// Start printing
function startMessage (logger, message, collapsed{
  const startMessage = collapsed// Whether to fold
    ? logger.groupCollapsed/ / folding
    : logger.group/ /

  // render
  // Demoted for compatibility
  try {
    startMessage.call(logger, message)
  } catch (e) {
    logger.log(message)
  }
}

// Finish printing
function endMessage (logger{
// Demoted for compatibility
  try {
    logger.groupEnd()
  } catch (e) {
    logger.log('- the log end -)}}function getFormattedTime ({
  const time = new Date(a)return ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`
}

/ / String. Prototype. Repeat polyfill
function repeat (str, times{
  return (new Array(times + 1)).join(str)
}

/ / String. Prototype. PadStart polyfill
function pad (num, maxLength{
  return repeat('0', maxLength - num.toString().length) + num
}
Copy the code

The above is the full content of vex4.0 source analysis, thank you for your attention, comments, likes and favorites! See you later.