Vue2. x source code just rough read finished, to tell the truth template compilation deep is really a moment to see, other almost understand, thinking that since all looked at vue that family bucket all looked at it, vuex pulled the source code, feel the amount of code looked much more comfortable, first solve this, follow-up to look at vue-router, Vuex reads version 3.6.2.

0. Directory structure

Code is really not much, the core code is even less, feel very suitable for just learn to read the source of the small white, the most important is the store constructor in store.js

1. The entrance

index.js

As usual, let’s start with import. What do we have here

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import createLogger from './plugins/logger'

// Export by default
export default {
  // Store constructor
  Store,
  Use () to execute the function
  install,
  version: '__VERSION__',
  
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}
// Export as needed
export {
  Store,
  install,
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}
Copy the code

2.Vue.use(Vuex)

Take a look at the Vuex website for a start

import Vue from 'vue'
import Vuex from 'vuex'
// Register the plug-in
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
Copy the code

By reading the Vue source code, or the documentation, we know when calling vue.use ()

  • The argument is a function. Execute it directly
  • The argument is an object when executed in the objectinstallmethods

install

// src/store.js
import applyMixin from './mixin'

// ...
export function install (_Vue) {
 if (Vue && _Vue === Vue) {
   if (__DEV__) {
     console.error(
       '[vuex] already installed. Vue.use(Vuex) should be called only once.')}return
 }
 // The Vue variable can only be registered once
 Vue = _Vue
 applyMixin(Vue)
}
Copy the code

mixin

X (Vue. Mixin); Vue. Mixin (Vue. We will not go into the details here, but will focus on versions 2.x and above

  1. Mixed in with abeforeCreateThe hook function of
  2. The function is going to be innew Vue(options)Catch in timeoptionsParameters of thestoreAnd mounted itvminstancevm.$store
// src/mixin.js
export default function (Vue) {
  const version = Number(Vue.version.split('. ') [0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    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

3.new Store()

Store the class

constructor

  1. Initialize some variables that are used internally
  2. withcallReencapsulateddispatch,commitMethod that makes the call mandatorythisPoint to the current instance
  3. Generate a module tree from the parameter object that returns the object at the time the module was collected, which will expose onerootattribute
  4. Initialize themodules
  5. Determine whetherstrict.In strict mode, an error is thrown whenever a state change occurs that is not caused by a mutation function
  6. resetStoreVM:stateandgetterresponsive
  7. To register the plugin
  8. devtoolsswitch
export class Store {
  constructor (options = {}) {
   
    // ...

    const {
      plugins = [],
      strict = false
    } = options

    // Initialize some variables for internal use
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    // The created object contains a root attribute that points to the root of the module tree
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

    // This encapsulates the Dispatch and commit methods, forcing this to refer to the store
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // Strict mode
    this.strict = strict
    // Get the state of the root node, this step builds the tree
    const state = this._modules.root.state
    // The tree is recursively traversed and mounted to the store's private properties, which can be accessed by the dot operator
    installModule(this, state, [], this._modules.root)
    // make the data responsive
    resetStoreVM(this, state)
    plugins.forEach(plugin= > plugin(this))
    
    // Enable devTools
    constuseDevtools = options.devtools ! = =undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)}}}Copy the code

ModuleCollection class

Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection: Modulecollection

The constructor

  • constructor: callregisterfromoptionsThe root begins recursive registration

methods

  • get(path)Path is an array of keys accessed by variablesArray.prpototype.reduceMethod to find values iteratively
  • getNamespace: generates a use from the module path/Delimited namespaces
  • update
  • register: Registration module, which is actually a spanning tree process, traverses the original object recursively,new ModuleGenerate the node and add it to the parent node
  • unregister
  • isRegistered
// src/moduel/module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // 根options
    this.register([], rawRootModule, false)
  }

  
  get (path) {
     // this.root is actually the entire argument element, iterating through the existing key of path to find the module object of path
    return path.reduce((module, key) = > {
      return module.getChild(key)
    }, this.root)
  }

  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) = > {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : ' ')},' ')
  }

  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }

  register (path, rawModule, runtime = true) {
    // ...
    
    // Create a new module instance from the current argument object
    const newModule = new Module(rawModule, runtime)
    // If the path array is empty, it is the root module
    if (path.length === 0) {
      this.root = newModule
    } else {
      // Find the parent module
      const parent = this.get(path.slice(0, -1))
      // Inherits the current module from the parent module
      parent.addChild(path[path.length - 1], newModule)
    }

    // If there are nested modules under the current module
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) = > {
        // The key here is actually the name of each module, adding the name of the module iterated through to the path as part of the module path
        // rawChildModule takes modules[key] and recurses
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    const child = parent.getChild(key)

    // ...
    if(! child.runtime) {return
    }

    parent.removeChild(key)
  }

  isRegistered (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]

    if (parent) {
      return parent.hasChild(key)
    }

    return false}}export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key= > fn(obj[key], key))
}
Copy the code

The Module class

Equivalent to a node in the tree, by declaring object parameters, to create a node of the module tree, the code is very concise and easy to understand, and the file contains only these codes

The constructor

  • constructor: Initialization parameter
    • runtime
    • _children
    • _rawModule
    • _state

attribute

  • namespaced: Whether there is a separate namespace

methods

  • addChild
  • removeChild
  • getChild
  • hasChild
  • update
  • forEachChild
  • forEachGetter
  • forEachAction
  • forEachMutation
// src/moduel/modules.js
export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    / / modules
    this._children = Object.create(null)
    // The original object
    this._rawModule = rawModule
    const rawState = rawModule.state

    / / save the state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  get namespaced () {
    return!!!!!this._rawModule.namespaced
  }

  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
  }

  update (rawModule) {
    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
    }
  }

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

  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

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

Copy the code

installModuel

  • It can be accessed through the dot operatorstate
  • Mount the input parameters tostoreOn, and unified format
function installModule (store, rootState, path, module, hot) {
  // Is it a wooden block
  constisRoot = ! path.length// Whether to have a namespace
  const namespace = store._modules.getNamespace(path)

  // Register the namespace
  if (module.namespaced) {
    // In the future we can find modules through namespaces
    store._modulesNamespaceMap[namespace] = module
  }

  // Set the access mode for state
  if(! isRoot && ! hot) {const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() = > {
      // Modules can be accessed through dot operators
      Vue.set(parentState, moduleName, module.state)
    })
  }
   
  // Create a temporary context for the following registration, in which the namespace can be omitted
  // In a way, it is a wrapper that prevents the following registrations from being written to the namespace
  const local = module.context = makeLocalContext(store, namespace, path)

  / / register Mutation
  module.forEachMutation((mutation, key) = > {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

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

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

  // Call this function recursively on the child module
  module.forEachChild((child, key) = > {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

Copy the code

makeLocalContext

Returns a context object containing

  • dispatch
  • commit
  • getter
  • state

Dispatch and commit are wrapped, and internal calls are automatically namespaces, so they can be omitted when written

function makeLocalContext (store, namespace, path) {
  // whether it is defined globally or has its own namespace
  const noNamespace = namespace === ' '

  // Define a store in the current namespace, which can be automatically added to the namespace
  const local = {
    dispatch: noNamespace ? store.dispatch : (_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._actions[type]) {console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          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

      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)
    }
  }
  
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () = > store.getters
        : () = > makeLocalGetters(store, namespace)
    },
    state: {
      get: () = > getNestedState(store.state, path)
    }
  })

  return local
}
Copy the code

RegisterMutation, registerAction, registerGetter

Format and register the various data types you enter

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // The return value of the action must be promise
    if(! isPromise(res)) { res =Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err= > {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

function registerGetter (store, type, rawGetter, local) {
  // ...
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters)}}Copy the code

resetStoreVM

Make store data as responsive as vUE

  1. Create a new onevueInstance, mount tostore._vm
  2. getterbecomecomputed
  3. statebecomedataA property in
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)
    // Expose access to the getter externally
    Object.defineProperty(store.getters, key, {
      get: () = > store._vm[key],
      enumerable: true // for local getters})})const silent = Vue.config.silent
  Vue.config.silent = true
  // Create a vue instance and mount it to store._VM
  // getter -> computed
  // state -> data
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // ..
}

// src/utils.js
export function partial (fn, arg) {
  return function () {
    return fn(arg)
  }
}
Copy the code

summary

Recall when the STORE API we used was mounted

  1. stateIn:installModuleIn theVue.set(parentState, moduleName, module.state)
  2. getterIn:resetStoreVMCreated in thevueInstance mount tostore._vmThen usedefinePrototypeThe agentstore.getter
  3. installModuleIs registered at the same timemutation,actionBut we don’t call these two directly, we call them throughcommitanddispatch