前言
在阅读commit源码之前,希望同学们能花几分钟时间温习一下commit的使用方式,以便快速理解。
源码解读
相关方法
commit 源码
commit (_type, _payload, _options) {
const {
type, // 触发的类型 ,例如:store.commit('increment')
payload, // 提交的载荷即参数,例如:store.commit('increment', 10)
options // 配置项
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload } // 定义mutation变量
// this._mutations,store实例定义的用于存储mutation的对象
const entry = this._mutations[type] // 获取相应类型的mutation
// entry不存在就阻止运行
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload) // 调用mutation,并传入参数
})
})
// this._subscribers,用于存储传入store.subscribe方法中的
// handler(一个函数,下面会介绍它是怎么存储的)。
// 遍历this._subscribers副本(调用slice返回的新数组),调用
// handler并将mutation和this.state作为参数传入。这也是为什么
// store.subscribe中的handler,能接受到这两个参数的原因。
// 使用slice进行浅复制,是为防止订阅者同步调用取消订阅而造成的迭
// 代器失效。
this._subscribers
.slice()
.forEach(sub => sub(mutation, this.state))
if (__DEV__ && options && options.silent) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
复制代码
为了更好的理解源码,我们还需要了解 Vuex.Store 的实例方法:subscribe
通过官方文档学习subscribe后,想必你已注意到下面这句话:
订阅 store 的 mutation。handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数
对于这段话,可以这样理解(个人理解思路,仅供参考):
-
调用store.subscribe,能够订阅 store 的 mutation
-
store.commit完成mutation提交后,会调用handler(调用store.subscribe时,传入的函数)。在上面的代码中,想必你已注意到,先在this._withCommit方法中调用mutation,然后在遍历this._subscribers,去调用handler,并传入 mutation 和经过 mutation 后的状态(就是修改后的state)作为参数。
只通过文字描述,可能还不够清晰。下面,我们就结合案例及其源码来看一下。
subscribe 案例
为了方便起见,使用了vuex提供的counter测试案例(目录位置:examples/counter)。
调用store实例方法 commit 和 subscribe:
打印参数:
接下来,我们还需要了解subscribe的源码。
subscribe 源码
subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
}
复制代码
-
genericSubscribe 参数介绍:
- fn:一个函数,接收 mutation 和经过 mutation 后的状态作为参数。fn是我们调用store.subscribe时传入的函数。
store.subscribe((mutation, state) => { console.log(mutation.type) console.log(mutation.payload) }) 复制代码
-
subs:指this._subscribers,用于存储handler也就是fn
-
options:配置项,添加 prepend: true ,可把处理函数添加到其链的最开始。
function genericSubscribe (fn, subs, options) {
// 首先判断fn是否已存在于subs即this._subscribers中。
// 若是不存在,就判断配置项options和options.prepend,如果都为真,
// 将fn插入到subs首位,否则插入到subs尾部
if (subs.indexOf(fn) < 0) {
options && options.prepend
? subs.unshift(fn)
: subs.push(fn)
}
// 返回当前的fn函数,这是subscribe可以取消订阅的关键所在
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
复制代码
看到这里,想必你已经对commit和subscribe有了更深的了解。现在,我们还需理解一个问题:为什么在this._subscribers上使用slice返回它的一个新数组后在遍历执行handler,就能在订阅者同步调用取消订阅时,防止迭代器失效呢?
细心的你,可能已经发现genericSubscribe函数的两个作用:
-
向this._subscribers中添加fn
-
返回当前的fn(通过调用subs.splice(i, 1)实现),用于取消订阅时调用。
调用store.subscribe会向this._subscribers中添加 fn,而调用store.subscribe返回的函数(取消订阅)则会从this._subscribers中删除 fn。
这两个操作,一加一减,都在变动this._subscribers,所以未了安全起见,就通过slice方法返回一个新的数组,然后在进行相应的操作,而不是直接去操作this._subscribers。