preface

Hi, I’m Wakawa. This is the fifth chapter to learn the overall architecture of the source code. Overall architecture this word seems to be a bit big, let’s say it is the overall structure of the source code, the main is to learn the overall structure of the code, do not go into other is not the main line of the specific function implementation. In this article, you’ll learn the code for the actual repository.

Learn the overall architecture of the source code series articles as follows:

1. Learn the overall architecture of jQuery source code, and create your own JS class library 2. Learn to use the whole framework of the source code to create their own functional programming library 3. Learning loDASH source code overall architecture, to create their own functional programming class library 4. Learn sentry source code overall architecture, build their own front-end exception monitoring SDK 5. Learn vuEX source code overall architecture, to create their own state management library 6. Learning axiOS source code overall architecture, to create their own request library 7. Learning koA source code overall architecture, a brief analysis of KOA onion model principle and CO principle 8. Learn redux source code overall architecture, in-depth understanding of Redux and its middleware principles

Interested readers can click to read. The next article might be to learn the axios source code.

Introduction Article more detailed introduction of VUEX, VUE source debugging method and VUEX principle. And detailed introduction of vuex. use installation and new vuex. Store initialization, vuex. Store API (such as Dispatch, Commit, etc.) implementation and auxiliary functions mapState, mapGetters, MapActions, mapMutations createNamespacedHelpers.

Chrome browser debugging vuex source method

The debugging vuex method is also available from the previous section, and is described here in detail to help readers who may not know how to debug the source Code. Can make this vuex – analysis of the source code analysis a warehouse fork or cloning directly down, git clone https://github.com/lxchuan12/vuex-analysis.git

One of the folders, vuex, is clone official vuex warehouse dev branch. As of now (November 2019), version is V3.1.2, last commit is BA2FF3A3, 2019-11-11 11:51 Ben Hutton. Includes author’s notes for easy understanding.

After the completion of the clone, the vuex/examples/webpack config. Add devtool js configuration.

// Add devTool configuration for easy debugging
devtool: 'source-map'.output: {}
Copy the code
git clone https://github.com/lxchuan12/vuex-analysis.git
cd vuex
npm i
npm run dev
Copy the code

Open http://localhost:8080/ and click on the example you want to open, for example: Shopping Cart source = > http://localhost:8080/shopping-cart/ open the control panel on the left to find webapck. / / SRC directory store files Breakpoint debugging can according to their needs.

This article is mainly through Shopping Cart, (path vuex/examples/ Shopping – Cart) example debugging code.

Incidentally, the method for debugging the vUE source code (v2.6.10)

git clone https://github.com/vuejs/vue.git
Copy the code

Add this –sourcemap to the package.json file after the script dev command.

{
  "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap"
}
Copy the code
git clone https://github.com/vuejs/vue.git
cd vue
npm i
//# sourceMappingURL=vue.js.map
npm run dev
# New terminal window Install http-server globally in root directory (one line command to start the service tool) npm i -g http-server hs -p 8100  # Change vuejs' index.html file vue.min.js to vue.js in the Examples folder Dist /vue.js = dist/vue.js = dist/vue.js = dist/vue.js = dist/vue.js  # browser open open http://localhost:8100/examples/  # Open the control panel source in the left to find the SRC directory that is vue.js source file according to their needs breakpoint debugging can be. Copy the code

This section is devoted to debugging methods. Because it really matters. You can debug the code, and it’s easy to look at the source code. Focus on the mainline debugging code, which is easy to understand. It is highly recommended to clone the author’s warehouse, debug the code themselves, look at the comments, do not debug the code, only read the article is not easy to absorb and digest. I also read at the end of the article I recommend to read the article, but still need to see their own source code, just know where to write these articles, where not detailed writing.

Start of text ~

Vuex principle

Let’s briefly explain how VUEX works

<template>
<div>
  count {{$store.state.count}}
</div>
</template>
Copy the code

Each component (i.e., Vue instance) mixes the same Store instance in the beforeCreate lifecycle as property $Store, which is why methods such as this.$store.dispatch can be called.

The final $store.state.count source displayed in the template looks like this.

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

$store._vm._data.? State. The count of vm. $store. _vm. _data while forming.? State is reactive. How do you do it in response? It’s new Vue()

function resetStoreVM (store, state, hot) {
  // Omit some code
  store._vm = new Vue({
    data: {
      $state: state
 },  computed  })  // Omit some code } Copy the code

The state here is the user-defined state. Computed in this case is the processed user-defined getters. The class Store API is all about modifying vm.$store._vm._data.? For state and computed services.

Vue. Use the installation

I drew a picture of the Vuex object, a plug-in for Vue.

Vuex object diagram

At this point, congratulations, you have understood how Vuex works. The article is long, if temporarily don’t want to focus on the source code details, can clone a git clone https://github.com/lxchuan12/vuex-analysis.git on our warehouse code, subsequent debugging code, will want to see again see thumb up collection.

Document the Vue. Use Vue. Use (Vuex)

Parameter: Object Function plugin Usage: install the vue. js plug-in. If the plug-in is an object, you must provide the install method. If the plug-in is a function, it is treated as the install method. When the install method is called, Vue is passed in as an argument. This method needs to be called before new Vue() is called. When the install method is called multiple times by the same plug-in, the plug-in will only be installed once.

For breakpoint debugging, take a look at the source code for vue.use.

function initUse (Vue) {
  Vue.use = function (plugin) {
    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
    // If it already exists, return this, which is Vue
    if (installedPlugins.indexOf(plugin) > - 1) {
 return this  }   // additional parameters  var args = toArray(arguments.1);  // Make this (Vue) the first item in the array  args.unshift(this);  // If the install property of the plug-in is a function, call it  if (typeof plugin.install === 'function') {  plugin.install.apply(plugin, args);  } else if (typeof plugin === 'function') {  // If the plug-in is a function, call it  // This is null for the apply(null) function in strict mode  plugin.apply(null, args);  }  // Add to the installed plug-in  installedPlugins.push(plugin);  return this  }; } Copy the code

Install function

vuex/src/store.js

export function install (_Vue) {
  // Vue already exists and is equal to vuex.use
  if (Vue && _Vue === Vue) {
    // Omit code: non-production environment error, vuex has been installed
    return
 }  Vue = _Vue  applyMixin(Vue) } Copy the code

Now let’s look at the applyMixin function

ApplyMixin function

vuex/src/mixin.js

export default function (Vue) {
  // Vue Version number
  const version = Number(Vue.version.split('. ') [0])
  if (version >= 2) {
    BeforeCreate = function in array [logon, logon];
 // The last call loops through the array, calling the functions. This is a combination of functions and functions.  // If we were to design it ourselves, what would it be?  Vue.mixin({ beforeCreate: vuexInit })  } else {  // Omit the version code for 1.x...  }   / * * * Vuex init hook, injected into each instances init hooks list. * /  function vuexInit () {  const options = this.$options  // store injection  // Store is injected into each instance of Vue  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

Finally, each Vue instance object has a $store attribute. It’s the same Store instance. Take the shopping cart example:

const vm = new Vue({
  el: '#app'.  store,
  render: h= > h(App)
})
console.log('vm.$store === vm.$children[0].$store', vm.$store === vm.$children[0].$store) // true console.log('vm.$store === vm.$children[0].$children[0].$store', vm.$store === vm.$children[0].$children[0].$store) // true console.log('vm.$store === vm.$children[0].$children[1].$store', vm.$store === vm.$children[0].$children[1].$store) // true Copy the code

The vuex.store constructor

Look at the finalnew Vuex.StoreAfter theStoreInstance object diagram: Get an idea.

export class Store {
  constructor (options = {}) {
    // This constructor is longer than this constructor
  }
}
Copy the code
if(! Vue &&typeof window! = ='undefined' && window.Vue) {
  install(window.Vue)
}
Copy the code

If the VUex plug-in is imported using CDN Script, the vuex plug-in is automatically installed. You do not need to use vue.use (vuex) to install the vuex plug-in.

// The asset function is implemented
export function assert (condition, msg) {
  if(! condition)throw new Error(`[vuex] ${msg}`)
}
Copy the code
if(process.env.NODE_ENV ! = ='production') {
  // You may be wondering why you don't use console.assert. An error in the console.assert function does not prevent subsequent code execution
  assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  assert(typeof Promise! = ='undefined'.`vuex requires a Promise polyfill in this browser.`)
  assert(this instanceof Store, `store must be called with the new operator.`)
} Copy the code

Conditional assertion: Throw an error if not satisfied

1. You must use vue.use (Vuex) to create a Store instance. Promise polyfill is required for vuex. 3.Store must be called using the new operator.

const {
  // The plugin defaults to an empty array
  plugins = [],
  // Strict mode is false by default
  strict = false
} = options Copy the code

Take the plugins and strict arguments from the user-defined new vuex.store (options).

// store internal state
// Store The state inside the instance object
this._committing = false
// Used to store user - defined actoins after processing
this._actions = Object.create(null)
// Used to store actions subscriptions this._actionSubscribers = [] // To store the user defined mutations after treatment this._mutations = Object.create(null) // This is used to store user-defined getters after processing this._wrappedGetters = Object.create(null) // Module collector to construct module tree structure this._modules = new ModuleCollection(options) // Used to store the relationship between the module namespaces this._modulesNamespaceMap = Object.create(null) / / subscribe this._subscribers = [] // Use $watch to observe getters this._watcherVM = new Vue() // Cache of generated local getters this._makeLocalGettersCache = Object.create(null) Copy the code

Declare some internal variables of the Store instance object. Used to store user-defined variables such as Actions, mutations, getters and so on after processing.

Mention the difference between object.create (null) and {}. The former has no prototype chain, the latter has. Object.create(null).__proto__ is undefined ({}).__proto__ is object.prototype

// 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.commit = function boundCommit (type, payload, options) {  return commit.call(store, type, payload, options) } Copy the code

Bind yourself to commit and Dispatch

Why bind like this? Dispach (” this “) and DISPach (” This “) are not necessarily store instances

// Strict mode, default is false
this.strict = strict
// State of the root module
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) Copy the code

InstallModule (this, state, [], this._modules.root)

Initialize the root module. It also recursively registers all submodules. And collect getters for all modules and put them in this._wrappedgetters.

resetStoreVM(this, state)

Initialize store._vm responsive and register _wrappedGetters as a computed attribute

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

Plug-ins: Execute all plug-ins by passing the instance object store to the plug-in function.

constuseDevtools = options.devtools ! = =undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
  devtoolPlugin(this)
}
Copy the code

Example Initialize the vue-devTool development tool. The devtools parameter is passed to fetch devTools or fetch vue.config. devtools configuration.

First read the full source code for this constructor. There are three things you should focus on. Respectively is:

this._modules = new ModuleCollection(options)
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)
Copy the code

This._modules = new ModuleCollection(options). If you don’t want to see this, you can see the return result. The installModule, resetStoreVM functions can be debugged at breakpoints.

class ModuleCollection

Collect modules and construct module tree structure.

Register the root module parameter rawRootModule, which is the options parameter of Vuex.Store. The raw module (user-defined), root module

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
} Copy the code
/ * ** Registration module* @param {Array} path Specifies the path* @param {Object} rawModule raw rawModule* @param {Boolean} Runtime Runtime default is true* / register (path, rawModule, runtime = true) {  // Non-production environment assertions determine whether the user-defined module meets the requirements  if(process.env.NODE_ENV ! = ='production') {  assertRawModule(path, rawModule)  }   const newModule = new Module(rawModule, runtime)  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 submodules recursively  if (rawModule.modules) {  forEachValue(rawModule.modules, (rawChildModule, key) => {  this.register(path.concat(key), rawChildModule, runtime)  })  } } Copy the code

class Module

// Base data struct for store's module, package with some attribute and method
// Store's module base data structure, including some properties and methods
export default class Module {
  constructor (rawModule, runtime) {
    // Receive the runtime parameter
 this.runtime = runtime  // Store some children item  // Store submodules  this._children = Object.create(null)  // Store the origin module object which passed by programmer  // Store the raw, raw module  this._rawModule = rawModule  / / module state  const rawState = rawModule.state   // Store the origin module's state  // The original Store may be a function or an object. If the original Store is false, the null object is assigned.  this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}  } } Copy the code

After a series of registrations, finallythis._modules = new ModuleCollection(options) this._modulesThe value of theta is going to look like this. The author drew a picture to show:

InstallModule function

function installModule (store, rootState, path, module, hot) {
  // Is the root module
  constisRoot = ! path.length  // Namespace string
  const namespace = store._modules.getNamespace(path)
 if (module.namespaced) {  // Omit code: the module namespace map object already exists  // Module is assigned to _modulesNamespaceMap[namespace]  store._modulesNamespaceMap[namespace] = module  }  / /... The subsequent code is moved out for interpretation } Copy the code

Register the state

// set state
// Not a root module and not a hot overload
if(! isRoot && ! hot) {  // Obtain the parent state
  const parentState = getNestedState(rootState, path.slice(0.- 1))
 // Module name  / / such as the cart  const moduleName = path[path.length - 1]  / / state registration  store._withCommit((a)= > {  // Omit code: non-production environment error module state repeated setting  Vue.set(parentState, moduleName, module.state)  }) } Copy the code

The result is a responsive data instance with a structure like this: store.state

{
  // Omit several attributes and methods
  Get state = get state = get state = get state = get state
  state: {
    cart: {
 checkoutStatus: null. items: []  }  } } Copy the code
const local = module.context = makeLocalContext(store, namespace, path)
Copy the code

Module. Context is assigned to the helper functions in helpers, mapState, mapGetters, mapMutations, and mapActions. Generate local dispatches, commits, getters, and states. The main function is to smooth the differentiation, do not need the user to transfer module parameters.

Iterate over the registration mutation

module.forEachMutation((mutation, key) = > {
  const namespacedType = namespace + key
  registerMutation(store, namespacedType, mutation, local)
})
Copy the code
/ * ** registered mutation* @param {Object} Store Object* @param {String} type Type* @param {Function} handler Specifies a user-defined Function* @param {Object} local local Object* / function registerMutation (store, type, handler, local) {  Mutations are collected to find the corresponding mutation function, and no mutations are assigned to the empty array  const entry = store._mutations[type] || (store._mutations[type] = [])  / / the last mutation  entry.push(function wrappedMutationHandler (payload) {  / * * * mutations: {  * pushProductToCart (state, { id }) {  * console.log(state); *}*}* Why is the first parameter of user-defined mutation state and the second parameter payload* /  handler.call(store, local.state, payload)  }) } Copy the code

Iterate over the registration action

module.forEachAction((action, key) = > {
  const type = action.root ? key : namespace + key
  const handler = action.handler || action
  registerAction(store, type, handler, local)
})
Copy the code
/ * ** registered mutation* @param {Object} Store Object* @param {String} type Type* @param {Function} handler Specifies a user-defined Function* @param {Object} local local Object* / function registerAction (store, type, handler, local) {  const entry = store._actions[type] || (store._actions[type] = [])  // Payload is the second argument to the actions function  entry.push(function wrappedActionHandler (payload) {  / * ** Why does the first argument of the user-defined actions function have* Cause of {dispatch, commit, getters, state, rootGetters, rootState} * actions: {  * checkout ({ commit, state }, products) {  * console.log(commit, state); *}*}* /  let res = handler.call(store, {  dispatch: local.dispatch,  commit: local.commit,  getters: local.getters,  state: local.state,  rootGetters: store.getters,  rootState: store.state  }, payload)  / * * * export function isPromise (val) {  return val && typeof val.then === 'function'  } * Determine why asynchronous functions are handled in actions if they are not promiseizedThat is why constructor assertions do not support promise errorsPromise polyfill is required for VUEXassert(typeof Promise ! == 'undefined', `vuex requires a Promise polyfill in this browser.`)* /  if(! isPromise(res)) { res = Promise.resolve(res)  }  // Devtool triggers vuex:error  if (store._devtoolHook) {  // Catch catch error  return res.catch(err= > {  store._devtoolHook.emit('vuex:error', err)  // Throw an error  throw err  })  } else {  // Then the function executes the result  return res  }  }) } Copy the code

Iterating over the registered getter

module.forEachGetter((getter, key) = > {
  const namespacedType = namespace + key
  registerGetter(store, namespacedType, getter, local)
})
Copy the code
/ * ** registered getter* @param {Object} store Store instance* @param {String} type Type* @param {Object} rawGetter * @param {Object} rawGetter * @param {Object} rawGetter* @examples For cartProducts: (state, getters, rootState, rootGetters) => {}* @param {Object} local Local Object* / function registerGetter (store, type, rawGetter, local) {  // If the type already exists, error: Already exists  if (store._wrappedGetters[type]) {  if(process.env.NODE_ENV ! = ='production') {  console.error(`[vuex] duplicate getter key: ${type}`)  }  return  }  // Otherwise: assign  store._wrappedGetters[type] = function wrappedGetter (store) {  / * ** This is why getters can get the values (state, getters, rootState, rootGetters) * getters = {  * cartProducts: (state, getters, rootState, rootGetters) => {  * console.log(state, getters, rootState, rootGetters); *}*}* /  return rawGetter(  local.state, // local state  local.getters, // local getters  store.state, // root state  store.getters // root getters  )  } } Copy the code

Iterate over the registration submodule

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

ResetStoreVM function

resetStoreVM(this, state, hot)

Initialize store._vm responsive and register _wrappedGetters as a computed attribute

function resetStoreVM (store, state, hot) {

  // Store a copy of the old Vue instance object _vm
  const oldVm = store._vm

 // bind store public getters  / / bind store. Getter  store.getters = {}  // reset local getters cache  // Reset the local getters cache  store._makeLocalGettersCache = Object.create(null)  // Register when collecting user defined wrappedGetters after processing  const wrappedGetters = store._wrappedGetters  // Declare a computed object  const computed = {}  // Run wrappedGetters on computed  forEachValue(wrappedGetters, (fn, key) => {  // use computed to leverage its lazy-caching mechanism  // direct inline function use will lead to closure preserving oldVm.  // using partial to return function with only arguments preserved in closure environment.  / * ** partial function* Execute function returns a new function export function partial (fn, arg) {  return function () {  return fn(arg)  }  } * /  computed[key] = partial(fn, store)  // Getters to assign keys  Object.defineProperty(store.getters, key, {  get: (a)= > store._vm[key],  // Can be enumerated  enumerable: true // for local getters  })  })   // use a Vue instance to store the state tree  // suppress warnings just in case the user has added  // some funky global mixins  // Use a Vue instance object to store the state tree  // Prevent warning users to add some global mixins   // The declaration variable silent stores the user-set silent mode configuration  const silent = Vue.config.silent  // Silent mode is enabled  Vue.config.silent = true  store._vm = new Vue({  data: {  $state: state  },  computed  })  // Assign back the stored silent mode configuration  Vue.config.silent = silent   // enable strict mode for new vm  // Execute this sentence in strict mode  // Observe state with $watch, and only use the mutation function (_withCommit)  if (store.strict) {  enableStrictMode(store)  }   // If there is an old _VM instance  if (oldVm) {  // Hot load is true  if (hot) {  // dispatch changes in all subscribed watchers  // to force getter re-evaluation for hot reloading.  // Set oldvm._data. $state = null  store._withCommit((a)= > {  oldVm._data.$state = null  })  }  // Instance destruction  Vue.nextTick((a)= > oldVm.$destroy())  } } Copy the code

With the constructor source code out of the way, let’s look at some API implementations for vuex.Store.

The vuex.store instance method

Vuex API documentation

commit

Submit a mutation.

commit (_type, _payload, _options) {
  // check object-style commit
  // Unify the object style
  const {
    type,
 payload,  options  } = unifyObjectStyle(_type, _payload, _options)   const mutation = { type, payload }  // Fetch the user-defined mutation after processing  const entry = this._mutations[type]  // Omit the warning code for non-production environments...  this._withCommit((a)= > {  // Traversal execution  entry.forEach(function commitIterator (handler) {  handler(payload)  })  })  // Subscribe to the mutation execution  this._subscribers.forEach(sub= > sub(mutation, this.state))   // Omit the warning code for non-production environments... } Copy the code

Commit supports multiple modes. Such as:

store.commit('increment', {
  count: 10
})
// Object submission mode
store.commit({
 type: 'increment'. count: 10 }) Copy the code

The unifyObjectStyle function returns {type, payload, options}.

dispatch

The distribution of the action.

dispatch (_type, _payload) {
  // check object-style dispatch
  // Obtain the type and payload parameters
  const {
    type,
 payload  } = unifyObjectStyle(_type, _payload)   // Declare the action variable equal to the type and payload parameters  const action = { type, payload }  // Entry, which is the _actions collection  const entry = this._actions[type]  // Omit the warning code for non-production environments...  try {  this._actionSubscribers  .filter(sub= > sub.before)  .forEach(sub= > sub.before(action, this.state))  } catch (e) {  if(process.env.NODE_ENV ! = ='production') {  console.warn(`[vuex] error in before action subscribers: `)  console.error(e)  }  }   const result = entry.length > 1  ? Promise.all(entry.map(handler= > handler(payload)))  : entry[0](payload)   return result.then(res= > {  try {  this._actionSubscribers  .filter(sub= > sub.after)  .forEach(sub= > sub.after(action, this.state))  } catch (e) {  if(process.env.NODE_ENV ! = ='production') {  console.warn(`[vuex] error in after action subscribers: `)  console.error(e)  }  }  return res  }) } Copy the code

replaceState

Replace the root state of the store and debug only with state merge or time travel.

replaceState (state) {
  this._withCommit((a)= > {
    this._vm._data.$state = state
  })
}
Copy the code

watch

Listen responsively for the return value of fn, and call the callback function when the value changes.

/ * ** Observe a value* @param {Function} Getters* @param {Function} cb callback* @param {Object} Options* / watch (getter, cb, options) {  if(process.env.NODE_ENV ! = ='production') {  assert(typeof getter === 'function'.`store.watch only accepts a function.`)  }  return this._watcherVM.$watch((a)= > getter(this.state, this.getters), cb, options) } Copy the code

subscribe

Subscribe to store mutation.

subscribe (fn) {
  return genericSubscribe(fn, this._subscribers)
}
Copy the code
// Collect subscribers
function genericSubscribe (fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
 return (a)= > {  const i = subs.indexOf(fn)  if (i > - 1) {  subs.splice(i, 1)  }  } } Copy the code

subscribeAction

Subscribe to the Store action.

subscribeAction (fn) {
  const subs = typeof fn === 'function' ? { before: fn } : fn
  return genericSubscribe(subs, this._actionSubscribers)
}
Copy the code

registerModule

Register a dynamic module.

/ * ** Dynamic registration module* @ param {Array | String} path path* @param {Object} rawModule raw rawModule* @param {Object} options* / registerModule (path, rawModule, options = {}) {  // If path is a string, convert it to an array  if (typeof path === 'string') path = [path]   // Omit the error code for non-production environments   // Manually call the method registered by the module  this._modules.register(path, rawModule)  // Install the module  installModule(this.this.state, path, this._modules.get(path), options.preserveState)  // reset store to update getters...  / / set resetStoreVM  resetStoreVM(this.this.state) } Copy the code

unregisterModule

Uninstall a dynamic module.

/ * ** Logout module* @ param {Array | String} path path* /
unregisterModule (path) {
 // If path is a string, convert it to an array  if (typeof path === 'string') path = [path]   // Omit the non-production error code...   // Call module logout manually  this._modules.unregister(path)  this._withCommit((a)= > {  // Log off the module  const parentState = getNestedState(this.state, path.slice(0.- 1))  Vue.delete(parentState, path[path.length - 1])  })  / / reset Store  resetStore(this) } Copy the code

hotUpdate

Hot replace the new action and mutation.

/ / thermal load
hotUpdate (newOptions) {
  // The update method of ModuleCollection is called, and the final call corresponds to the update of each Module
  this._modules.update(newOptions)
  / / reset Store
 resetStore(this.true) } Copy the code

Helper functions for component binding

File path: vuex/ SRC /helpers.js

mapState

Create computed properties for the component to return the state in the Vuex Store.

export const mapState = normalizeNamespace((namespace, states) = > {
  const res = {}
  States must be either an array or an object for non-production environments
  if(process.env.NODE_ENV ! = ='production' && !isValidMap(states)) {
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
 }  normalizeMap(states).forEach(({ key, val }) = > {  res[key] = function mappedState () {  let state = this.$store.state  let getters = this.$store.getters  // The namespace parameter is passed  if (namespace) {  // Use namespace to find a module from the store.  const module = getModuleByNamespace(this.$store, 'mapState', namespace)  if (!module) {  return  }  state = module.context.state  getters = module.context.getters  }  return typeof val === 'function'  ? val.call(this, state, getters)  : state[val]  }  // Tag for vuex for easy display in DevTools  // mark vuex getter for devtools  res[key].vuex = true  })  return res }) Copy the code

NormalizeNamespace Specifies the standardized unified namespace

function normalizeNamespace (fn) {
  return (namespace, map) = > {
    // Namespace is not passed, swap parameters, namespace is an empty string
    if (typeofnamespace ! = ='string') {
      map = namespace
 namespace = ' '  } else if (namespace.charAt(namespace.length - 1)! = ='/') {  // If it is a string, the last character is not/adding /  // Because _modulesNamespaceMap stores such a structure.  / * * * _modulesNamespaceMap:  cart/: {}  products/: {}  } * * /  namespace += '/'  }  return fn(namespace, map)  } } Copy the code
// Check whether map is an array or an object.
function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}
Copy the code
/ * * * Normalize the map
* Normalize the map and return an array * 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)) { return []  }  return Array.isArray(map)  ? map.map(key= > ({ key, val: key }))  : Object.keys(map).map(key= > ({ key, val: map[key] })) } Copy the code

Module. Context is assigned to the helper functions in helpers, mapState, mapGetters, mapMutations, and mapActions.

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

In this case, the difference is erased by getting the commit, dispatch, state, and getters without the user passing the namespace

getModuleByNamespace

function getModuleByNamespace (store, helper, namespace) {
  // the _modulesNamespaceMap variable is assigned in class Store installModule
  const module = store._modulesNamespaceMap[namespace]
  if(process.env.NODE_ENV ! = ='production'&&!module) {
    console.error(`[vuex] module namespace not found in ${helper}() :${namespace}`)
 }  return module } Copy the code

After watching these, and finally, for example: vuex/examples/shopping cart/components/ShoppingCart vue

computed: {
. mapState({      checkoutStatus: state= > state.cart.checkoutStatus
    }),
}
Copy the code

In the absence of namespaces, you end up with something like this

computed: {
    checkoutStatus: this.$store.state.checkoutStatus
}
Copy the code

Let’s say I have the namespace ‘ruochuan’,

computed: {
. mapState('ruochuan', {
      checkoutStatus: state= > state.cart.checkoutStatus
    }),
}
Copy the code

Will be converted to:

computed: {
    checkoutStatus: this.$store._modulesNamespaceMap.['ruochuan/'].context.checkoutStatus
}
Copy the code

mapGetters

Create computed properties for the component to return the return value of the getter.

export const mapGetters = normalizeNamespace((namespace, getters) = > {
  const res = {}
  Getters must be an array or an object for non-production environments
  normalizeMap(getters).forEach(({ key, val }) = > {
    // The namespace has been mutated by normalizeNamespace
 val = namespace + val  res[key] = function mappedGetter () {  if(namespace && ! getModuleByNamespace(this.$store, 'mapGetters', namespace)) {  return  }  // Omit the code: the getter cannot be matched  return this.$store.getters[val]  }  // mark vuex getter for devtools  res[key].vuex = true  })  return res }) Copy the code

For example:

computed: {
. mapGetters('cart', {
    products: 'cartProducts'.    total: 'cartTotalPrice'
  })
}, Copy the code

Finally convert to:

computed: {
  products: this.$store.getters['cart/cartProducts'].  total: this.$store.getters['cart/cartTotalPrice'].}
Copy the code

mapActions

Create component methods to distribute actions.

export const mapActions = normalizeNamespace((namespace, actions) = > {
  const res = {}
  // Omit code: the non-production actions parameter must be an array or an object
  normalizeMap(actions).forEach(({ key, val }) = > {
    res[key] = function mappedAction (. args) {
 // get dispatch function from store  let dispatch = this.$store.dispatch  if (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

mapMutations

Create component method commit mutation. MapMutations are similar to mapActions, except dispatch instead of commit.

let commit = this.$store.commit
commit = module.context.commit
return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
Copy the code

vuex/src/helpers

MapMutations and mapActions

{
  methods: {
. mapMutations(['inc']),
. mapMutations('ruochuan'['dec']),
. mapActions(['actionA'])
. mapActions('ruochuan'['actionB'])  } } Copy the code

And eventually convert to

{
  methods: {
inc(... args){      return this.$store.dispatch.apply(this.$store, ['inc'].concat(args))
    },
dec(... args){ return this.$store._modulesNamespaceMap.['ruochuan/'].context.dispatch.apply(this.$store, ['dec'].concat(args))  }, actionA(... args){ return this.$store.commit.apply(this.$store, ['actionA'].concat(args))  } actionB(... args){ return this.$store._modulesNamespaceMap.['ruochuan/'].context.commit.apply(this.$store, ['actionB'].concat(args))  }  } } Copy the code

As you can see, these helper functions are extremely convenient for developers.

createNamespacedHelpers

Create namespace-based component binding helper functions.

export const createNamespacedHelpers = (namespace) = > ({
  // bind(null) in strict mode, this refers to null, such as napState
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
 mapActions: mapActions.bind(null, namespace) }) Copy the code

You put these helper functions in an object.

The plug-in

Vuex/SRC /plugins/devtool vuex/ SRC /plugins/logger

The article is longer, so I won’t go into this part. Specific can see the author’s warehouse vuex-analysis vuex/ SRC /plugins/ source annotations.

conclusion

Article more detailed introduction of VUEX, VUE source debugging method and VUEX principle. And detailed introduction of vuex. use installation and new vuex. Store initialization, vuex. Store API (such as Dispatch, Commit, etc.) implementation and auxiliary functions mapState, mapGetters, MapActions, mapMutations createNamespacedHelpers.

Article comments, in the VUex-Analysis source code warehouse basically have comment analysis, find a star. Again, it is highly recommended to clone the code down.

git clone https://github.com/lxchuan12/vuex-analysis.git
Copy the code

Print the Store instance first, see the specific structure, and then combined with instance breakpoint debugging, get twice the result with half the effort.

Vuex source code is relatively few, more than a thousand lines after packaging, very worth learning, but also relatively easy to read.

If the reader finds something wrong or can be improved, or if there is anything that is not clear, feel free to comment. In addition, I think it is well written, and there is some help for you. I can like it, comment on it, and retweet it. It is also a kind of support for the author.

Recommended reading

Vuex official document VUex Github warehouse Meituan Ming Origin: VUEX framework principle and source code analysis this article is strongly recommended, process picture is very good Zhihu Huang Yi: VUEX 2.0 source code analysis this article is also strongly recommended, about the more comprehensive bug cancer: Vuex source code analysis (how to read the source code practice) this article is also strongly recommended, mainly about how to read the source code dye momo: Vuex source code analysis netease Koala front-end team: Vuex source code analysis YCK: Vuex source code in-depth analysis xiao ShengFang: 【 Front-end dictionary 】 from the source interpretation of Vuex into Vue life cycle process

Another series by the author

How can you simulate the JS call and apply methods? How can you simulate the bind method? How can you simulate the new operator

about

Author: often with the name of Ruochuan mixed in rivers and lakes. The front road lovers | | PPT know little, only good study. Personal blog – if the use of vuepress reconstruction, reading experience may be better dig gold column, welcome to pay attention to ~ segmentfault front view column, welcome to pay attention to ~ zhihu front view column, welcome to pay attention to ~ github blog, related source code and resources are put here, seek a star^_^~

Welcome to add wechat communication wechat public account

May be more interesting wechat public number, long press scan code concern. You can also add wechat Ruochuan12, indicate the source, pull you into [front view communication group].

If sichuan vision

This article was typeset using MDNICE