添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
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源码也解读了,现在终于补上了。同时,若是小伙伴们看到了这篇文章并发现了有解读错误的地方,还请不吝赐教,大家一起共同进步加掉发。

    分类:
    前端
    标签: