vuex 原始碼:深入 vuex 之 action

cobish發表於2018-03-28

前言

mutation 用於同步更新 state,而 action 則是提交 mutation,並可進行非同步操作,從而間接更新 state。

在還沒解讀之前,大家都認為 action 相對於 mutation 來說難理解得多,畢竟涉及到了非同步。本篇就來看看 action 是如何處理非同步的?

準備

解讀之前,你可能需要對 promise 的相關知識有所瞭解:

  1. Promise.resolve()
  2. Promise.all()

解讀

在解讀前我們要熟悉 action 的用法,這裡就直接解讀,不再列出 action 的使用方式了。

建構函式 constructor 看起,只看與 action 相關的程式碼:

constructor (options = {}) {
  this._actions = Object.create(null)

  // bind commit and dispatch to self
  const store = this
  const { dispatch } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
  }

  installModule(this, state, [], options)
}
複製程式碼

action 的註冊

installModule 裡實現 action 的註冊,然後就是 dispatch 的實現。定位到 installModule 方法。

function installModule (store, rootState, path, module, hot) {
  const {
    actions
  } = module

  if (actions) {
    Object.keys(actions).forEach(key => {
      registerAction(store, key, actions[key], path)
    })
  }
}
複製程式碼

程式碼裡迴圈 options 的 actions,將其作為引數傳入 registerAction 方法,定位到 registerAction 方法中(自動忽略 path):

function registerAction (store, type, handler, path = []) {
  const entry = store._actions[type] || (store._actions[type] = [])
  const { dispatch, commit } = store
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch,
      commit,
      getters: store.getters,
      state: getNestedState(store.state, path),
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }

    return res
  })
}
複製程式碼

先找出裡面需用到的 isPromise 方法,它的功能主要就判斷是否有 then 函式:

export function isPromise (val) {
  return val && typeof val.then === 'function'
}
複製程式碼

與 mutation 類似,註冊時將 action 儲存到了 store._actions 陣列裡面,為了使用 modules 的情況下 commit 時能觸發相同名稱的 action。

action 的註冊比 mutation 多了一步,那就是將函式做一些處理,並將值返回。那到底做了什麼處理呢?它會先使用 isPromise 方法判斷,如果否,再使用 Promise.resolve() 進行處理。為什麼要這樣處理呢,主要是為了能夠一個 action 執行完能夠使用 then 繼續處理下一個非同步操作

上面的話有點難懂。如果 action 返回的是 Promise 物件比較好理解,因為能夠使用 then 繼續下一個操作。如果 action 沒返回或者返回的不是 Promise 物件,那它就沒有 then 函式可以下一步操作,看下程式碼示例吧:

actions: {
  actionA ({ commit }) {
    commit('someMutation')
  }
}

return dispatch('actionA').then(() => {
  commit('someOtherMutation')
})
複製程式碼

上面的 actionA 在 dispatch 後沒返回,如果沒有使用 Promise.resolve() 進行處理,那麼執行到 then 時就會報錯了,那麼接下去的操作也就無法執行了。

dispatch 的實現

再來看看 dispatch 的實現:

dispatch (type, payload) {
  const entry = this._actions[type]

  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}
複製程式碼

如果只有一個 action 的比較好理解,直接執行返回 promise 物件即可。但當同名的 action 不止一個,則會使用 Promise.all() 處理,當所有的 action 執行完畢後這個 dispatch 才算執行完畢,才會執行 then 函式。

總結

至此,action 的解讀完畢。可以把 action 看成是 mutation 的升級版。

在註冊 action 時,action 函式執行完畢後會將返回值作為一個 Promise 物件返回,以便可以讓非同步按順序執行。

在呼叫 dispatch 時,一個 store.dispatch 在不同模組中可以觸發多個 action 函式。在這種情況下,只有當所有觸發函式完成後,返回的 Promise 才會執行。

相關文章