持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
前言
大家好,上一次为每个组件添加store属性分享中我们解析了vuex是如何为每个组件的实例挂载$store属性的,那么这也仅仅是Vuex初始化时的一个过程,下面还有一些关于mutations,actions,modules等等相关的操作。接下来的分享中我们将继续解读Vuex的源码,探秘mutations、actions和modules等各个模块是如何工作的。
new Store()都干了啥
本次分享还是基于vuex3.2版本进行源码解读
上篇分享中我们提到Vuex是一个包含了install方法和Store类的对象,并且对install方法的实现原理进行了源码分析,解析来还剩下一个vuex中的核心模块 - Sotre类,那么我们就先来看下在new Store的时候都做了写什么事
constructor (options = {}) {
// store internal state
this._committing = false
...
this._makeLocalGettersCache = Object.create(null)
// 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)
}
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)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
以上是Store类构造函数中的部分主要代码,在该构造函数中主要做了如下这么几件事:
- 给store实例(this)挂载一些
_xxx
的变量供Store内部使用,主要用于存储一些内部状态 - 给store实例(this)本身绑定dispatch和commit方法,而在该方法中最终调用的是Store原型对象上对应的方法
- const { dispatch, commit } = this这句代码中实际上获取的是Store原型上对应的两个方法,因为这个时候this(store实例本身)上还没有这两个方法
- 通过this.commit = xxx 和 this.dispatch = xxx来给store实例本身也添加两个对应的方法,但在方法内部最终执行的还是原型上的方法
- 调用installModule安装模块,因为如果项目比较大的话,一般不会将所有的状态都放在同一个vuex模块中管理,而是拆分成不同的模块,这种情况下就需要将所有的模块都需要安装注册。从注释来看该方法主要做了三件事:
- 初始化根模块
- 递归注册所有子组件
- 收集所有模块的getters放到当前实例的_wrappedGetters中
- 调用resetStoreVM方法,该方法主要用于:
- 初始化store实例上的vm,也就是给store实例上也挂载一个Vue的实例
- 把上面的_wrappedGetters 注册为一个计算属性
- 最后安装插件
从源码上来看,我们在new Store的时候就做了以上这么几件事,那么其核心内容还是在
installModule
和resetStoreVM
中。
installModule
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
...
// set state
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)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
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.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
以上是installModule的主要代码,其主要做了三件事:
- 命名空间的检查(该部分代码已省略),默认情况所有的模块都是定义在全局命名空间中的,但vuex也允许我们使用单独的命名空间,所以这里会做个命名空间的处理
- 如果当前模块不是根模块,则会调用store实例的_withCommit函数并传递一个新函数,其目的就是让这个新函数在_withCommit中执行。
- 在该新函数体中其核心操作就是通过调用Vue的set方法,将子模块中的state分别添加的根模块的state中,并且让这些state也是响应式
- 接下来第三件事就是注册各个模块中的mutation、action和getter以及安装子模块
- registerMutation注册mutation
- registerAction注册action
- registerGetter注册getter
- installModule按钮子模块
registerMutation
function registerMutation(store, type, handler, local){
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload){
handler.call(store, local.state, payload)
})
}
registerMutation方法比较简单,核心功能就是向entry数组中添加一个wrappedMutationHandler函数
- registerMutation方法接收4个参数分别是:
- store: Store的实例
- type:type就是我们在mutation对象中定义的同步函数的
函数名
- handler:handler则是mutation对象中对应同步函数的定义,如 function xxx(){xxx}
- local:local是一个包含了dispatch、commit、getters和state的对象
- 第一句先定义一个变量entry,然后根据方法名称(type)到实例对象的_mutations中查找是否存在,如果存在将对应值赋值给entry,否则赋值一个空数组给entry
- 说明:_mutations是在new Store时定义的一个空对象,因此初次进来时_mutations还是空的,肯定找不到type对应的值,所以这里entry会被赋值为一个空数组
- 然后通过push向数组entry中添加一个一个函数wrappedMutationHandler,用于执行我们在mutation对象中定义的同步方法 到此就完成了mutation的注册,那了个么当我们通过this.$store.commet()去提交一个方法的时候,在它的背后又是如何处理的呢,接下来我们就再来挖一挖commit的源码。
commit
commit (_type, _payload, _options) {
// check object-style commit
const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
//...省略部分校验代码
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
//...省略部分校验代码
}
function unifyObjectStyle(type, payload, options){
if(isObject(type) && type.type){
options = payload
payload = type
type = type.type
}
return {type, payload, options}
}
在Store的原型对象上有这么个commit方法,该方法就是我们常用的this.$store.commit中的commit方法,我们来看看它都做了哪些事。
- 首先该函数接收三个参数:
- _type: 对应的是mutation对象中的方法名(字符串类型)
- _payload:一般情况下是一个对象,该对象中包含了我们调用mutation中的方法时要传递的参数
- _option:额外的参数,我们在调用commit时一般好像用不到
- 函数体内的第一句代码调用了个unifyObjectStyle方法,又从该方法中重新解析出了对应的三个参数,其中还给出注释说:check object-style commit,那这个玩意是干嘛的呢?其实说到这里就不得不提一句关于commit调用时传递参数的方式了。commit在调用时有两种传参的方式:
- 第一种就是第一个参数为字符串,第二个参数是对象的形式传递,也是我们通常用的比较多的一种方式,如
this.$store.commit('changeName',{name:'西瓜watermelon'})
- 另外一种方式则是传递一个包含了
type
对象,所有参数包括type都放在对象中,如
this.$store.commit({type:'changeName',name:'西瓜watermelon'})
因此这里的unifyObjectStyle方法就是为了把对象中的方法名(type)跟具体的参数区分开来
- 接下来就是根据方法名(type)从_mutations对象中找到对应的值,其值是一个数组(在上一步我们讲注册mutation时保存进去的)而数组中保存的是一个名为wrappedMutationHandler的函数
- 最后就是调用Store实例上的_withCommit方法,该方法接收一个匿名函数作为实参,而在该匿名函数中会遍历entry数组中的所有方法,并让方法执行
- 另外,该匿名函数会在_withCommit中被调用(前面也有提到过),该匿名函数执行的时候实际上就是让entry数组中的方法执行,而数组中的方法执行实际上调用的就是wrappedMutationHandler,让wrappedMutationHandler执行,而这个方法的内部最终调用的则是我们在mutation对象中定义的那个真正的改变state值的方法
总结
本次分享中我们主要解读了new Store、registerMutation和commit方法的源码,简单概括一下就是:
在new Store时会初始化一堆实例属性,然后就是通过installModule安装模块,在安装模块的时候会注册mutation、action、getter等,那么在注册mutation时会向数组entry中添加一个wrappedMutationHandler函数,而在该函数内部调用真正的我们在mutation对象中定义的方法,然后再把该数组保存在一个以方法名为属性的对象_mutation中,当我们外部调用commit的时候就会从该对象中找出对应方法名的数组,最后遍历该数组让数组中的wrappedMutationHandler执行,进而让我们定义的方法执行来改变state中的值
本次分享就到这里了,喜欢的朋友欢迎点个赞哦!