前言
在阅读 dispatch 源码之前,希望同学们能花几分钟时间温习一下它的使用方式,以便快速理解。
源码解读
相关方法
- unifyObjectStyle 检测参数类型并进行相应调整。详细介绍(提示:跳转后,向下滚动)。
dispatch 源码
dispatch (_type, _payload) {
// 检测参数类型并进行相应调整
const {
type, // 触发的类型 ,例如:store.dispatch('increment')
payload // 提交的载荷即参数,例如:store.dispatch('increment', 10)
} = unifyObjectStyle(_type, _payload)
const action = {
type,
payload
}
// this._actions,store实例定义的用于存储action的对象
const entry = this._actions[type]
// entry不存在就阻止运行
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
// this._actionSubscribers,用于存储调用store.subscribeAction时,传入的函数
// 使用slice进行浅复制,是为防止订阅者同步调用取消订阅而造成的迭代器失效。
// filter将符合条件(包含before属性)的元素筛选出来,组成一个新数组,然后遍历
// 此数组中的元素并调用before。这个筛选条件与store实例方法:subscribeAction
// 有关(稍后会介绍它)。
this._actionSubscribers
.slice()
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
// 使用Promise.all的原因:一个store.dispatch在不同模块中可以触发多个action函数。
// 在这种情况下,只有当所有触发函数完成后,返回的Promise才会执行。
const result = entry.length > 1 ?
Promise.all(entry.map(handler => handler(payload))) : entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
try {
// filter将符合条件(包含after属性)的元素筛选出来,组成一个新数组,然后遍历
// 此数组中的元素并调用after。这个筛选条件与store实例方法:subscribeAction有关。
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
// filter将符合条件(包含error属性)的元素筛选出来,组成一个新数组,然后遍历
// 此数组中的元素并调用error。这个筛选条件与store实例方法:subscribeAction有关。
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
为了更好的理解源码中对this._actionSubscribers的操作,我们需要了解一下Vuex.Store的实例方法:subscribeAction。
学习这个方法时,我们可以看到对其作用的定义:
它是用来订阅store的action。handler会在每个action分发的时候调用并接收action描述和当前的store的state这两个参数
若是,对这段话不甚清楚的话,可以多看几遍官方的举例:
store.subscribeAction((action, state) => {
console.log(action.type)
console.log(action.payload)
})
- handler指的就是传入subscribeAction方法的函数。
- 能接收action描述和当前的store的state这两个参数,是因为在对this._actionSubscribers进行遍历时就已传入。
光靠文字描述,还不能够太清晰。所以,我们就结合案例及其源码来看一下吧。
subscribeAction 案例
所用案例,是vuex项目中提供的counter测试案例(目录位置:examples/counter)。
调用store实例方法 dispatch 和 subscribeAction:
参数打印:
图中,我们不仅打印了传入的参数,也打印了this._actionSubscribers。初始时,它是个空数组, 一旦调用store.subscribeAction,这个数组中就会被放入一个函数,就是传入subscribeAction的handler。
subscribeAction 源码
subscribeAction (fn, options) {
const subs = typeof fn === 'function' ? {
before: fn
} : fn
return genericSubscribe(subs, this._actionSubscribers, options)
}
如果fn是个函数,就将一个对象赋值给subs,此对象,以before为键,fn为值。若是,你有仔细阅读vuex中有关subscribeAction的教程的话,想必你已明白,这么做的原由,也就是下面这句话:
从 3.1.0 起,subscribeAction 也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前)。
这是相应的代码部分,引用自官方文档。大家可以自行调试一下。
store.subscribeAction({
before: (action, state) => {
console.log(`before action ${action.type}`)
},
after: (action, state) => {
console.log(`after action ${action.type}`)
}
})
关于 genericSubscribe 方法,我在上一篇文章中已有介绍,这里不再重复。同时,关于在this._actionSubscribers上调用slice方法的原因,和上一篇文章谈到的对this._subscribers调用slice方法的原因是一样的(要仔细阅读一遍哟,一通百通嘛)。
一个store.dispatch在不同模块中可以触发多个action函数
还记得下面这段代码吗?这是dispatch源码中的另一重要知识点。
const result = entry.length > 1 ?
Promise.all(entry.map(handler => handler(payload))) : entry[0](payload)
在我们学习vuex关于Actions的教程时,你是否注意的最后一句话: 一个store.dispatch在不同模块中可以触发多个action函数。在这种情况下,只有当所有触发函数完成后,返回的Promise才会执行。
如你所想,这句话的对应实现,就是上面这段代码,其中关键就是Promise.all的使用。为更加清晰地理解上面的语句,我们举例说明一下。
代码部分
位于vuex目录中的位置:examples/counter/store.js,在根模块和moduleB模块中注册一样的 action:increment,为了简便,仅贴出了需要改动的部分,其它部分都一样。
// 根模块和moduleB模块都注册一样的 increment
const actions = {
increment: ({ commit }) => commit('increment'),
}
const moduleB = {
// namespaced: true,
actions: {
increment: ({ commit }) => commit('increment')
}
}
调用
位于vuex目录中的位置:examples/counter/Counter.vue,直接在mounted生命周期函数中调用,方便测试。
mounted () {
// 在这里调用,仅是为了便于测试
this.$store.dispatch('increment')
},
打印参数
位于vuex目录中的位置:src/store.js,找到dispatch源码部分打印参数。
结果
store.dispatch().then()
大家到知道,调用store。dispatch分发action时,可以使用then,就是下面代码展示的那样。
store.dispatch('actionA').then(() => {
// ...
})
至于原因,若是大家已仔细阅读dispatch源码,那么便知道,它最后返回了一个Promise对象。
结束语
最近有点忙,在解读完commit源码后,一直想把dispatch源码也解读了,现在终于补上了。同时,若是小伙伴们看到了这篇文章并发现了有解读错误的地方,还请不吝赐教,大家一起共同进步加掉发。