vuex 源码分析(5)—— commit

前言

在阅读commit源码之前,希望同学们能花几分钟时间温习一下commit的使用方式,以便快速理解。

源码解读

相关方法

  • unifyObjectStyle 检测参数类型并进行相应调整。详细介绍

  • _withCommit 避免修改状态时报错。详细介绍

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 后的状态作为参数

对于这段话,可以这样理解(个人理解思路,仅供参考):

  1. 调用store.subscribe,能够订阅 store 的 mutation

  2. store.commit完成mutation提交后,会调用handler(调用store.subscribe时,传入的函数)。在上面的代码中,想必你已注意到,先在this._withCommit方法中调用mutation,然后在遍历this._subscribers,去调用handler,并传入 mutation 和经过 mutation 后的状态(就是修改后的state)作为参数。

只通过文字描述,可能还不够清晰。下面,我们就结合案例及其源码来看一下。

subscribe 案例

为了方便起见,使用了vuex提供的counter测试案例(目录位置:examples/counter)。

调用store实例方法 commit 和 subscribe:

1622100475(1).jpg

1622100331(1).jpg

打印参数:

WeChat1b0e42951618cbe78ee09a649a1a5f90.png

接下来,我们还需要了解subscribe的源码。

subscribe 源码

  subscribe (fn, options) {
    return genericSubscribe(fn, this._subscribers, options)
  }
复制代码
  • genericSubscribe 参数介绍:

    1. fn:一个函数,接收 mutation 和经过 mutation 后的状态作为参数。fn是我们调用store.subscribe时传入的函数。
    store.subscribe((mutation, state) => {
      console.log(mutation.type)
      console.log(mutation.payload)
    })
    复制代码
    1. subs:指this._subscribers,用于存储handler也就是fn

    2. 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函数的两个作用:

  1. 向this._subscribers中添加fn

  2. 返回当前的fn(通过调用subs.splice(i, 1)实现),用于取消订阅时调用。

调用store.subscribe会向this._subscribers中添加 fn,而调用store.subscribe返回的函数(取消订阅)则会从this._subscribers中删除 fn

这两个操作,一加一减,都在变动this._subscribers,所以未了安全起见,就通过slice方法返回一个新的数组,然后在进行相应的操作,而不是直接去操作this._subscribers。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享