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 object
install
methods
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
- Mixed in with a
beforeCreate
The hook function of - The function is going to be in
new Vue(options)
Catch in timeoptions
Parameters of thestore
And mounted itvm
instancevm.$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
- Initialize some variables that are used internally
- with
call
Reencapsulateddispatch
,commit
Method that makes the call mandatorythis
Point to the current instance - Generate a module tree from the parameter object that returns the object at the time the module was collected, which will expose one
root
attribute - Initialize the
modules
- Determine whether
strict
.In strict mode, an error is thrown whenever a state change occurs that is not caused by a mutation function resetStoreVM
:state
andgetter
responsive- To register the plugin
devtools
switch
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
: callregister
fromoptions
The root begins recursive registration
methods
get(path)
Path is an array of keys accessed by variablesArray.prpototype.reduce
Method to find values iterativelygetNamespace
: generates a use from the module path/
Delimited namespacesupdate
register
: Registration module, which is actually a spanning tree process, traverses the original object recursively,new Module
Generate the node and add it to the parent nodeunregister
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 parameterruntime
_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 operator
state
- Mount the input parameters to
store
On, 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
- Create a new one
vue
Instance, mount tostore._vm
getter
becomecomputed
state
becomedata
A 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
state
In:installModule
In theVue.set(parentState, moduleName, module.state)
getter
In:resetStoreVM
Created in thevue
Instance mount tostore._vm
Then usedefinePrototype
The agentstore.getter
installModule
Is registered at the same timemutation
,action
But we don’t call these two directly, we call them throughcommit
anddispatch