「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
vuex初始化
一般使用Vuex的步骤如下:
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
new Vue({
el: '#app',
store
})
Vue.use(Vuex)调用分析
Vue.use(Vuex)同vue-router一样,调用的是install方法(可见这里)。主要定义如下:
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
将传入的Vue实例对象重新传入applyMixin函数, applyMixin函数定义在mixin.js中。
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
// ....
}
function vuexInit () {
const options = this.$options
// store injection
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
}
}
}
兼容Vue1.0版本的不做分析,Vue2.x同vue-router大致相同,向Vue实例全局beforeCreate混入函数vuexInit,使在Vue组件里通过this.$store能访问到Vuex实例。
Vuex实例初始化分析
在使用Vuex时,会通过new Vuex.store({}) 将Vuex实例传入Vue实例中,从而让Vue实例中访问this.store访问到Vuex实例。 Vuex.store方法定义在store.js文件中。主要是对我们传入的options进行初始化,一般传入的多为modules,如下:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.store({
modules: {
a: moduleA,
b: moduleB
}
}
以上面的options为例,从初始化模块,安装模块和初始化store._vm分析。
初始化模块
export class Store {
constructor(options = {}) {
// ...
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
installModule(this, state, [], this._modules.root)
}
}
在Store类的初始化中,通过实例化ModuleCollection实现。
ModuleCollection定义在module-collection文件中,具体内容如下:
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
register (path, rawModule, runtime = true) {
if (__DEV__) {
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
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
当实例化ModuleCollection时,会调用this.register方法将我们定义的options传入,register方法的主要逻辑如下:
- register方法会实例化Module,当初始化时path的长度为0,会将实例化Module赋值给this.root。
- 如果我们定义的options有子modules,会递归调用this.register
- 当递归调用this.register时path不为空,会通过this.get方法传入path参数获取到当前moduler的父级Module实例并调用addChildren方法,添加Module实例的父子关系。
通过这一系列的操作,最终this.root会构造成一颗Module实例树,与我们传入的modules相对应。
Module类定义在module.js文件中,具体代码如下:
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
getChild (key) {
return this._children[key]
}
addChild (key, module) {
this._children[key] = module
}
}
它主要作用如下:
- 对我们传入的每一个module赋值给this._rawModule,之后就可以通过Module实例中的this._rawModule获取到我们每一个module所配置的getters.actions,mutation等。
- 将我们每一个module所定义的state赋值给this.state,需要注意的是如果state为函数时,会执行后再赋值。
综上,模块初始化的主要作用是将我们传入的options根据module层级关系生成一颗Module实例树,在Store实例中能通过this._modulesrrr的root属性访问到,每一个Module实例具有访问到当前module所定义的state,getters,actions,muation的方法和属性。
安装模块分析
安装模块同初始化模块一样,也定义在store.js文件中
export class Store {
constructor (options = {} ) {
// ....
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
installModule(this. state, [], this._modules.root)
}
}
安装模块通过installModule方法实现,传入Store的this指向, 根Module实例的state属性,空的path数组, 根Module实例。
installModule同样定义在store.js文件中,它主要实现的功能如下:
-
构建_modulesNamespaceMap
const namespace = store._modules.getNamespace(path) // ... // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module }
获取当前path(即当前module实例)所对应的命名空间字符串, store._modules.getNamespace(path)方法定义在module-collection.js文件中。
getNamespace (path) { let module = this.root return path.reduce((namespace, key) => { module = module.getChild(key) return namespace + (module.namespaced ? key + '/' : '') }, '') }
this.root是在初始化模块时所构建的Module实例树,它通过reduce方法一层层的查找path所对应的Module实例并获取到它的namespaced属性(返回true或false)。然后返回path数组所对应的namespace字符串。该字符串表征的是当前Module实例所对应的namespace。
在instllModule方法中,将获取到的namespace字符串保存到Store实例中的_modulesNamespaceMap中,建立namespaces与其对应Moduel实例的字典。
-
构建一个本地上下文环境
const local = module.context = makeLocalContext(store, namespace, path)
makeLocalContext方法定义在这里,它的作用是根据当前Module实例是否具有命名空间,来定义dispath,commit,get,state方法。如果没有命名空间则调用Store实例所定义的相关方法。有就将传入的type加上namespace再调用Store实例所定义的相关方法并传入type参数。
-
注册Module相关实例方法
module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) })
调用Module实例的方法,对我们传入options的mutaion,action,getter方法进行依次注册。注册方法大致相同。
-
registerMutation方法
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) }) }
将定义的每一个mutation函数包裹到wrappedMutationHandler函数中并已type为key,添加到Store实例中的_mutations[type]数组中,方便后续调用。
-
registerAction方法
registerAction方法与registerMutation方法大致相同,不过会确保返回的wrappedActionHandler是一个异步函数
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) if (!isPromise(res)) { res = Promise.resolve(res) } // ... return res }
-
registerGetter方法
函数会在wrappedGetter函数中返回rawGetter, rawGetter是我们在options中定义的getters函数。
function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }
-
-
递归调用installModule
最后installModule会递归调用自身,将之前构建的Module实例依次执行installModule方法。
module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })
在installModule方法中,如果是递归调用的,会执行下面的逻辑:
if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) }
获取到父Module实例所对应的state,并通过Vue.set设置parentState对象,使其能通过path访问到子Module实例的state。
综上整个安装模块的主要过程是对Module实例树递归调用递归调用installModule方法,构建Store实例的_mutations,_actions,_wrappedGetters字典,使其与我们定义的options中的方法一一对应。并且使其能通过根Module根实例的state访问到所有的Module实例中的state属性。
初始化Store._vm
在Store实例化中,会调用以下代码执行初始化Store._vm
resetStoreVM(this, state)
function resetStoreVM (store, state, hot) {
// ...
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
resetStoreVM的主要作用是创建一个新的Vue实例store._vm,通过在该实例中定义computed和data,建立store.getters和store.state之间的联系。当我们访问Store实例的state时,实际会访问到store._vm._data.$$state
get state () {
return this._vm._data.$$state
}
而在上文中store_wrappedGetters会传入store.state:
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
这样当store.state发生改变时,通过computed方法定义的getters也会重新计算。从而构建起getters和state的联系。
相关API调用流程分析
主要的API包括commit,getters,dispatch等,主要都是对数据的获取和存储。
数据获取
-
通过store.state获取 在上文中,我们知道访问store.state最终会访问到this._modules.root.state
get state () { return this._vm._data.$$state } function resetStoreVM(store, state, hot) { // ... store._vm = new Vue({ data: { $$state: state } }) } const state = this._modules.root.state installModule(this, state, [], this._modules.root) resetStoreVM(this, state)
而this._modules.root.state在installModule里,我们已经将它与作为path的module name绑定起来,所以可以通过store.state.a.b.XXX的形式去获取数据。
const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] Vue.set(parentState, moduleName, module.state
-
通过store.getters获取
当我们通过option将getters传入时,从上文可以得知会在installModule时注册
module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) 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 ) } }
rawGetter就是我们传入的getters函数,它会传入state和getters,从而能够访问到store.state里的数据。
数据存储
-
mutation提交
主要是通过commit去提交一个mutation的形式去提交修改state,我们定义的mutation函数通过option传入后最终会存储在store._mutations并传入state方便修改数据。
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) }) }
当我们调用commit去提交一个mutation时,会根据我们传入的type去查找store._mutations里对应的mutations函数并执行
commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) }
-
action提交
主要是通过action提交一个mutation来修改数据,他与mutation的不同主要是异步操作,它也在installModule里将其存储在store._mutations中。之后我们可以通过dispatch函数执行它。
dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { // ... resolve(res) }, error => { // ... reject(error) }) }) }
同commit的逻辑大致一致,所不同的是它是异步的。
参考资料
总结
主要分析了Vuex如何初始化和安装我们的module以及当调用commit,dispatch时访问并state的具体原理。