1. 程式人生 > >Vuex 2.0 原始碼分析(上)

Vuex 2.0 原始碼分析(上)

當我們用 Vue.js 開發一箇中到大型的單頁應用時,經常會遇到如下問題:

  • 如何讓多個 Vue 元件共享狀態
  • Vue 元件間如何通訊

通常,在專案不是很複雜的時候,我們會利用全域性事件匯流排 (global event bus)解決,但是隨著複雜度的提升,這些程式碼將變的難以維護。因此,我們需要一種更加好用的解決方案,於是,Vuex 誕生了。

本文並不是 Vuex 的科普文章,對於還不瞭解 Vuex 的同學,建議先移步 Vuex 官方文件;看英文文件吃力的同學,可以看 Vuex 的中文文件

 

Vuex 的設計思想受到了 Flux,Redux 和 The Elm Architecture 的啟發,它的實現又十分巧妙,和 Vue.js 配合相得益彰,下面就讓我們一起來看它的實現吧。

目錄結構

Vuex 的原始碼託管在 github,我們首先通過 git 把程式碼 clone 到本地,選一款適合自己的 IDE 開啟原始碼,展開 src 目錄,如下圖所示:

 

src 目錄下的檔案並不多,包含幾個 js 檔案和 plugins 目錄, plugins 目錄裡面包含 2 個 Vuex 的內建外掛,整個原始碼加起來不過 500-600 行,可謂非常輕巧的一個庫。

麻雀雖小,五臟俱全,我們先直觀的感受一下原始碼的結構,接下來看一下其中的實現細節。

原始碼分析

本文的原始碼分析過程不會是自上而下的給程式碼加註釋,我更傾向於是從 Vuex 提供的 API 和我們的使用方法等維度去分析。Vuex 的原始碼是基於 es6 的語法編寫的,對於不瞭解 es6 的同學,建議還是先學習一下 es6。

從入口開始

看原始碼一般是從入口開始,Vuex 原始碼的入口是 src/index.js,先來開啟這個檔案。

我們首先看這個庫的 export ,在 index.js 程式碼最後。

export default {
  Store,
  install,
  mapState,
  mapMutations,
  mapGetters,
  mapActions
}

這裡可以一目瞭然地看到 Vuex 對外暴露的 API。其中, Store 是 Vuex 提供的狀態儲存類,通常我們使用 Vuex 就是通過建立 Store 的例項,稍後我們會詳細介紹。接著是 install 方法,這個方法通常是我們編寫第三方 Vue 外掛的“套路”,先來看一下“套路”程式碼:

function install (_Vue) {
  if (Vue) {
    console.error(
      '[vuex] already installed. Vue.use(Vuex) should be called only once.'
    )
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

我們實現了一個 install 方法,這個方法當我們全域性引用 Vue ,也就是 window 上有 Vue 物件的時候,會手動呼叫 install 方法,並傳入 Vue 的引用;當 Vue 通過 npm 安裝到專案中的時候,我們在程式碼中引入第三方 Vue 外掛通常會編寫如下程式碼:

import Vue from 'vue'
import Vuex from 'vuex'
...
Vue.use(Vuex)

當我們執行 Vue.use(Vuex) 這句程式碼的時候,實際上就是呼叫了 install 的方法並傳入 Vue 的引用。install 方法顧名思義,現在讓我們來看看它的實現。它接受了一個引數 _Vue,函式體首先判斷 Vue ,這個變數的定義在 index.js 檔案的開頭部分:

let Vue // bind on install

對 Vue 的判斷主要是保證 install 方法只執行一次,這裡把 install 方法的引數 _Vue 物件賦值給 Vue 變數,這樣我們就可以在 index.js 檔案的其它地方使用 Vue 這個變量了。install 方法的最後呼叫了 applyMixin 方法,我們順便來看一下這個方法的實現,在 src/mixin.js 檔案裡定義:

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
    Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

這段程式碼的作用就是在 Vue 的生命週期中的初始化(1.0 版本是 init,2.0 版本是 beforeCreated)鉤子前插入一段 Vuex 初始化程式碼。這裡做的事情很簡單——給 Vue 的例項注入一個 $store 的屬性,這也就是為什麼我們在 Vue 的元件中可以通過 this.$store.xxx 訪問到 Vuex 的各種資料和狀態。

認識 Store 建構函式

我們在使用 Vuex 的時候,通常會例項化 Store 類,然後傳入一個物件,包括我們定義好的 actions、getters、mutations、state等,甚至當我們有多個子模組的時候,我們可以新增一個 modules 物件。那麼例項化的時候,到底做了哪些事情呢?帶著這個疑問,讓我們回到 index.js 檔案,重點看一下 Store 類的定義。Store 類定義的程式碼略長,我不會一下就貼上所有程式碼,我們來拆解分析它,首先看一下建構函式的實現:

class Store {
  constructor (options = {}) {
    assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
    assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)

    const {
      state = {},
      plugins = [],
      strict = false
    } = options

    // store internal state
    this._options = options
    this._committing = false
    this._actions = Object.create(null)
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._runtimeModules = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

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

    // strict mode
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], options)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

    // apply plugins
    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
  }
  ...
}  

建構函式的一開始就用了“斷言函式”,來判斷是否滿足一些條件。

assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)

這行程式碼的目的是確保 Vue 的存在,也就是在我們例項化 Store 之前,必須要保證之前的 install 方法已經執行了。

assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)

這行程式碼的目的是為了確保 Promsie 可以使用的,因為 Vuex 的原始碼是依賴 Promise 的。Promise 是 es6 提供新的 API,由於現在的瀏覽器並不是都支援 es6 語法的,所以通常我們會用 babel 編譯我們的程式碼,如果想使用 Promise 這個 特性,我們需要在 package.json 中新增對 babel-polyfill 的依賴並在程式碼的入口加上 import 'babel-polyfill' 這段程式碼。

再來看看 assert 這個函式,它並不是瀏覽器原生支援的,它的實現在 src/util.js 裡,程式碼如下:

export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

非常簡單,對 condition 判斷,如果不不為真,則丟擲異常。這個函式雖然簡單,但這種程式設計方式值得我們學習。

再來看建構函式接下來的程式碼:

const {
  state = {},
  plugins = [],
  strict = false
} = options

這裡就是利用 es6 的結構賦值拿到 options 裡的 state,plugins 和 strict。state 表示 rootState,plugins 表示應用的外掛、strict 表示是否開啟嚴格模式。

接著往下看:

// store internal state
this._options = options
this._committing = false
this._actions = Object.create(null)
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._runtimeModules = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()

這裡主要是建立一些內部的屬性:
this._options 儲存引數 options。
this._committing 標誌一個提交狀態,作用是保證對 Vuex 中 state 的修改只能在 mutation 的回撥函式中,而不能在外部隨意修改 state。
this._actions 用來儲存使用者定義的所有的 actions。
this._mutations 用來儲存使用者定義所有的 mutatins。
this._wrappedGetters 用來儲存使用者定義的所有 getters 。
this._runtimeModules 用來儲存所有的執行時的 modules。
this._subscribers 用來儲存所有對 mutation 變化的訂閱者。
this._watcherVM 是一個 Vue 物件的例項,主要是利用 Vue 例項方法 $watch 來觀測變化的。

繼續往下看:

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

// strict mode
this.strict = strict

這裡的程式碼也不難理解,把 Store 類的 dispatch 和 commit 的方法的 this 指標指向當前 store 的例項上,dispatch 和 commit 的實現我們稍後會分析。this.strict 表示是否開啟嚴格模式,在嚴格模式下會觀測所有的 state 的變化,建議在開發環境時開啟嚴格模式,線上環境要關閉嚴格模式,否則會有一定的效能開銷。

Vuex 的初始化核心

installModule

我們接著往下看:

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], options)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

// apply plugins
plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))

這段程式碼是 Vuex 的初始化的核心,其中,installModule 方法是把我們通過 options 傳入的各種屬性模組註冊和安裝;resetStoreVM 方法是初始化 store._vm,觀測 state 和 getters 的變化;最後是應用傳入的外掛。

下面,我們先來看一下 installModule 的實現:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const {
    state,
    actions,
    mutations,
    getters,
    modules
  } = module

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, state || {})
    })
  }

  if (mutations) {
    Object.keys(mutations).forEach(key => {
      registerMutation(store, key, mutations[key], path)
    })
  }

  if (actions) {
    Object.keys(actions).forEach(key => {
      registerAction(store, key, actions[key], path)
    })
  }

  if (getters) {
    wrapGetters(store, getters, path)
  }

  if (modules) {
    Object.keys(modules).forEach(key => {
      installModule(store, rootState, path.concat(key), modules[key], hot)
    })
  }
}

installModule 函式可接收5個引數,store、rootState、path、module、hot,store 表示當前 Store 例項,rootState 表示根 state,path 表示當前巢狀模組的路徑陣列,module 表示當前安裝的模組,hot 當動態改變 modules 或者熱更新的時候為 true。

先來看這部分程式碼:

 const isRoot = !path.length
 const {
   state,
   actions,
   mutations,
   getters,
   modules
 } = module

程式碼首先通過 path 陣列的長度判斷是否為根。我們在建構函式呼叫的時候是 installModule(this, state, [], options),所以這裡 isRoot 為 true。module 為傳入的 options,我們拿到了 module 下的 state、actions、mutations、getters 以及巢狀的 modules。

接著看下面的程式碼:

// set state
if (!isRoot && !hot) {
  const parentState = getNestedState(rootState, path.slice(0, -1))
  const moduleName = path[path.length - 1]
  store._withCommit(() => {
    Vue.set(parentState, moduleName, state || {})
  })
}

這裡判斷當不為根且非熱更新的情況,然後設定級聯狀態,這裡乍一看不好理解,我們先放一放,稍後來回顧。

再往下看程式碼:

if (mutations) {
  Object.keys(mutations).forEach(key => {
    registerMutation(store, key, mutations[key], path)
  })
}

if (actions) {
  Object.keys(actions).forEach(key => {
    registerAction(store, key, actions[key], path)
  })
}

if (getters) {
  wrapGetters(store, getters, path)
}

這裡分別是對 mutations、actions、getters 進行註冊,如果我們例項化 Store 的時候通過 options 傳入這些物件,那麼會分別進行註冊,我稍後再去介紹註冊的具體實現。那麼到這,如果 Vuex 沒有 module ,這個 installModule 方法可以說已經做完了。但是 Vuex 巧妙了設計了 module 這個概念,因為 Vuex 本身是單一狀態樹,應用的所有狀態都包含在一個大物件內,隨著我們應用規模的不斷增長,這個 Store 變得非常臃腫。為了解決這個問題,Vuex 允許我們把 store 分 module(模組)。每一個模組包含各自的 state、mutations、actions 和 getters,甚至是巢狀模組。所以,接下來還有一行程式碼:

if (modules) {
  Object.keys(modules).forEach(key => {
    installModule(store, rootState, path.concat(key), modules[key], hot)
  })
}

這裡通過遍歷 modules,遞迴呼叫 installModule 去安裝子模組。這裡傳入了 store、rootState、path.concat(key)、和 modules[key],和剛才不同的是,path 不為空,module 對應為子模組,那麼我們回到剛才那段程式碼:

// set state
if (!isRoot && !hot) {
  const parentState = getNestedState(rootState, path.slice(0, -1))
  const moduleName = path[path.length - 1]
  store._withCommit(() => {
    Vue.set(parentState, moduleName, state || {})
  })
}

當遞迴初始化子模組的時候,isRoot 為 false,注意這裡有個方法getNestedState(rootState, path),來看一下 getNestedState 函式的定義:

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

這個方法很簡單,就是根據 path 查詢 state 上的巢狀 state。在這裡就是傳入 rootState 和 path,計算出當前模組的父模組的 state,由於模組的 path 是根據模組的名稱 concat 連線的,所以 path 的最後一個元素就是當前模組的模組名,最後呼叫

store._withCommit(() => {
  Vue.set(parentState, moduleName, state || {})
}) 

把當前模組的 state 新增到 parentState 中。
這裡注意一下我們用了 store._withCommit 方法,來看一下這個方法的定義:

_withCommit (fn) {
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}

由於我們是在修改 state,Vuex 中所有對 state 的修改都會用 _withCommit函式包裝,保證在同步修改 state 的過程中 this._committing 的值始終為true。這樣當我們觀測 state 的變化時,如果 this._committing 的值不為 true,則能檢查到這個狀態修改是有問題的。

看到這裡,有些同學可能會有點困惑,舉個例子來直觀感受一下,以 Vuex 原始碼中的 example/shopping-cart 為例,開啟 store/index.js,有這麼一段程式碼:

export default new Vuex.Store({
  actions,
  getters,
  modules: {
    cart,
    products
  },
  strict: debug,
  plugins: debug ? [createLogger()] : []
})

這裡有兩個子 module,cart 和 products,我們開啟 store/modules/cart.js,看一下 cart 模組中的 state 定義,程式碼如下:

const state = {
  added: [],
  checkoutStatus: null
}

我們執行這個專案,開啟瀏覽器,利用 Vue 的除錯工具來看一下 Vuex 中的狀態,如下圖所示:

 

可以看到,在 rootState 下,分別有 cart 和 products 2個屬性,key 根據模組名稱而來,value 就是在每個模組檔案中定義的 state,這就把模組 state 掛載到 rootState 上了。

我們瞭解完巢狀模組 state 是怎麼一回事後,我們回過頭來看一下 installModule 過程中的其它 3 個重要方法:registerMutation、registerAction 和 wrapGetters。顧名思義,這 3 個方法分別處理 mutations、actions 和 getters。我們先來看一下 registerMutation 的定義:

registerMutation

function registerMutation (store, type, handler, path = []) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler(getNestedState(store.state, path), payload)
  })
}

registerMutation 是對 store 的 mutation 的初始化,它接受 4 個引數,store為當前 Store 例項,type為 mutation 的 key,handler 為 mutation 執行的回撥函式,path 為當前模組的路徑。mutation 的作用就是同步修改當前模組的 state ,函式首先通過 type 拿到對應的 mutation 物件陣列, 然後把一個 mutation 的包裝函式 push 到這個陣列中,這個函式接收一個引數 payload,這個就是我們在定義 mutation 的時候接收的額外引數。這個函式執行的時候會呼叫 mutation 的回撥函式,並通過 getNestedState(store.state, path) 方法得到當前模組的 state,和 playload 一起作為回撥函式的引數。舉個例子:

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

這裡我們定義了一個 mutation,通過剛才的 registerMutation 方法,我們註冊了這個 mutation,這裡的 state 對應的就是當前模組的 state,n 就是額外引數 payload,接下來我們會從原始碼分析的角度來介紹這個 mutation 的回撥是何時被呼叫的,引數是如何傳遞的。

我們有必要知道 mutation 的回撥函式的呼叫時機,在 Vuex 中,mutation 的呼叫是通過 store 例項的 API 介面 commit 來呼叫的,來看一下 commit 函式的定義:

commit (type, payload, options) {
  // check object-style commit
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }
  const mutation = { type, payload }
  const entry = this._mutations[type]
  if (!entry) {
    console.error(`[vuex] unknown mutation type: ${type}`)
    return
  }
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })
  if (!options || !options.silent) {
    this._subscribers.forEach(sub => sub(mutation, this.state))
  }
}

commit 支援 3 個引數,type 表示 mutation 的型別,payload 表示額外的引數,options 表示一些配置,比如 silent 等,稍後會用到。commit 函式首先對 type 的型別做了判斷,處理了 type 為 object 的情況,接著根據 type 去查詢對應的 mutation,如果找不到,則輸出一條錯誤資訊,否則遍歷這個 type 對應的 mutation 物件陣列,執行 handler(payload) 方法,這個方法就是之前定義的 wrappedMutationHandler(handler),執行它就相當於執行了 registerMutation 註冊的回撥函式,並把當前模組的 state 和 額外引數 payload 作為引數傳入。注意這裡我們依然使用了 this._withCommit 的方法提交 mutation。commit 函式的最後,判斷如果不是靜默模式,則遍歷 this._subscribers,呼叫回撥函式,並把 mutation 和當前的根 state 作為引數傳入。那麼這個 this._subscribers 是什麼呢?原來 Vuex 的 Store 例項提供了 subscribe API 介面,它的作用是訂閱(註冊監聽) store 的 mutation。先來看一下它的實現:

subscribe (fn) {
  const subs = this._subscribers
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

subscribe 方法很簡單,他接受的引數是一個回撥函式,會把這個回撥函式儲存到 this._subscribers 上,並返回一個函式,當我們呼叫這個返回的函式,就可以解除當前函式對 store 的 mutation 的監聽。其實,Vuex 的內建 logger 外掛就是基於 subscribe 介面實現對 store 的 muation的監聽,稍後我們會詳細介紹這個外掛。

registerAction

在瞭解完 registerMutation,我們再來看一下 registerAction 的定義:

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)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
 }

registerAction 是對 store 的 action 的初始化,它和 registerMutation 的引數一致,和 mutation 不同一點,mutation 是同步修改當前模組的 state,而 action 是可以非同步去修改 state,這裡不要誤會,在 action 的回撥中並不會直接修改 state ,仍然是通過提交一個 mutation 去修改 state(在 Vuex 中,mutation 是修改 state 的唯一途徑)。那我們就來看看 action 是如何做到這一點的。

函式首先也是通過 type 拿到對應 action 的物件陣列,然後把一個 action 的包裝函式 push 到這個陣列中,這個函式接收 2 個引數,payload 表示額外引數 ,cb 表示回撥函式(實際上我們並沒有使用它)。這個函式執行的時候會呼叫 action 的回撥函式,傳入一個 context 物件,這個物件包括了 store 的 commit 和 dispatch 方法、getter、當前模組的 state 和 rootState 等等。接著對這個函式的返回值做判斷,如果不是一個 Promise 物件,則呼叫 Promise.resolve(res) 給res 包裝成了一個 Promise 物件。這裡也就解釋了為何 Vuex 的原始碼依賴 Promise,這裡對 Promise 的判斷也和簡單,參考程式碼 src/util.js,對 isPromise 的判斷如下:

export function isPromise (val) {
  return val && typeof val.then === 'function'
}

其實就是簡單的檢查物件的 then 方法,如果包含說明就是一個 Promise 物件。

接著判斷 store._devtoolHook,這個只有當用到 Vuex devtools 開啟的時候,我們才能捕獲 promise 的過程中的 。 action 的包裝函式最後返回 res ,它就是一個地地道道的 Promise 物件。來看個例子:

actions: {
  checkout ({ commit, state }, payload) {
    // 把當前購物車的商品備份起來
    const savedCartItems = [...state.cart.added]
    // 傳送結帳請求,並愉快地清空購物車
    commit(types.CHECKOUT_REQUEST)
    // 購物 API 接收一個成功回撥和一個失敗回撥
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失敗操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

這裡我們定義了一個 action,通過剛才的 registerAction 方法,我們註冊了這個 action,這裡的 commit 就是 store 的 API 介面,可以通過它在 action 裡提交一個 mutation。state 對應的就是當前模組的 state,我們在這個 action 裡即可以同步提交 mutation,也可以非同步提交。接下來我們會從原始碼分析的角度來介紹這個 action 的回撥是何時被呼叫的,引數是如何傳遞的。

我們有必要知道 action 的回撥函式的呼叫時機,在 Vuex 中,action 的呼叫是通過 store 例項的 API 介面 dispatch 來呼叫的,來看一下 dispatch 函式的定義:

 dispatch (type, payload) {
  // check object-style dispatch
   if (isObject(type) && type.type) {
     payload = type
     type = type.type
   }
   const entry = this._actions[type]
   if (!entry) {
     console.error(`[vuex] unknown action type: ${type}`)
     return
   }
   return entry.length > 1
     ? Promise.all(entry.map(handler => handler(payload)))
     : entry[0](payload)
 }

dispatch 支援2個引數,type 表示 action 的型別,payload 表示額外的引數。前面幾行程式碼和 commit 介面非常類似,都是找到對應 type 下的 action 物件陣列,唯一和 commit 不同的地方是最後部分,它對 action 的物件陣列長度做判斷,如果長度為 1 則直接呼叫 entry[0](payload), 這個方法就是之前定義的 wrappedActionHandler(payload, cb),執行它就相當於執行了 registerAction 註冊的回撥函式,並把當前模組的 context 和 額外引數 payload 作為引數傳入。所以我們在 action 的回撥函式裡,可以拿到當前模組的上下文包括 store 的 commit 和 dispatch 方法、getter、當前模組的 state 和 rootState,可見 action 是非常靈活的。
連結:http://www.imooc.com/article/14739