vuex 源碼:深刻 vuex 之 module

前言

store 將應用的狀態集中起來,但若是應用變得很是複雜時,即狀態很是的多時,store 就有可能變得至關臃腫。module 可以幫 store 劃分了模塊,每一個模塊都擁有本身的 state、getter、mutation、action 和 module。vue

那麼 module 又是怎樣進行劃分的,劃分後的模塊又是如何管理本身的狀態呢?接下來就來解讀 module 的實現吧。vuex

準備

解讀前,須要對如下知識有所瞭解:api

  1. Array.prototype.reduce()
  2. Vue.set()

解讀

在 vuex 文檔裏有這麼一句話:默認狀況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的——這樣使得多個模塊可以對同一 mutation 或 action 做出響應。數組

什麼意思呢?先看看如下示例:模塊化

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    addNote () {
      console.log('root addNote')
    }
  },
  modules: {
    a: {
      state: {
        count: 0
      },
      mutations: {
        addNote () {
          console.log('module a addNote')
        }
      }
    }
  }
})
複製代碼

使用了 module 以後,state 則會被模塊化。好比要調用根模塊的 state,則調用 store.state.count,若是要調用 a 模塊的 state,則調用 store.state.a.countui

可是示例中的 mutation 則是註冊在全局下的,即調用 store.commit('addNote'),將會調用跟模塊和 a 模塊的 mutation。除非區分各模塊 mutation 的命名,不然,在同名的狀況下,只要 commit 後就會被觸發調用。spa

固然,vuex 2.0.0 後面的版本添加了命名空間 的功能,使得 module 更加的模塊化。prototype

因此接下來要解讀的 module 中,實際上只要 state 是被模塊化了, action、mutation 和 getter 仍是在全局的模塊下。code

modules 的註冊

installModule 裏實現了 module 的註冊,定位到 installModule 方法。遞歸

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const {
    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 || {})
    })
  }
  
  // mutation 的註冊
  // action 的註冊
  // getter 的註冊

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

看到簡化後的代碼,能夠看出 installModule 對 module 作了兩步初始化操做。第一步是使用 Vue.set() 對當前的 module 的 state 設置了監聽;第二步則是繼續遍歷子模塊,而後遞歸調用 installModule。

set state

因此 modules 的核心實現就在於對當前的 module 的 state 設置了監聽,將此段代碼提取出來:

const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
  Vue.set(parentState, moduleName, state || {})
})
複製代碼

先猜想 getNestedState 方法能夠獲取到父 state。因此先取得父 state,再取得當前模塊名稱,最後使用 Vue.set() 將當前的 state 設置在父 state 下。實際上該實現就是在一個 vue 實例下爲 data.state 添加屬性,並可以使得 vue 實例可以監聽到添加屬性的改動。

getNestedState

const parentState = getNestedState(rootState, path.slice(0, -1))
複製代碼

經過 path.slice(0, -1) 將當前模塊去掉,做爲參數和 rootState 根狀態傳入 getNestedState 方法中,返回了當前模塊的父狀態 parentState。

來看看 getNestedState 的實現:

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

若是 length 等於 0,即只有根 state,直接返回。另外一種狀況,若是有嵌套的模塊,那麼經過 Array.prototype.reduce() 方法一直往根 state 的屬性取 path 對應的 state 並返回。

至此,state 的模塊化已經註冊完成,而後遞歸調用 installModule 完成全部 module 的註冊。

既然是往 rootState 裏添加屬性,那麼獲取則能夠經過 store.state.a 來獲取到模塊,而後再繼續獲取模塊裏的 state。

get modules state

以前在解讀 mutation 和 action 的時候,一直都將 getNestedState 這個方法給省略了。在註冊 mutation 和 action 的時候,會出現如下這段代碼:

getNestedState(store.state, path)
複製代碼

實際上這段代碼就是獲取當前 modules 的 state,而後做爲參數回傳。

存放數組

還記得解讀 mutation 的時候,說到爲何會將 mutation 保存到了 store._mutations 數組裏面。主要目的是將全部 module 裏的 mutation 都存放在一個數組中,以便於在 commit 的時候能觸發全部 mutation。

getter 和 action 用到數組存放也是這樣一個緣由。

可是,若是兩個 module 裏有相同的 mutation 名稱,vuex 2.0.0 裏作不到只觸發其中一個 mutation。這個在日後的版本中設置命名空間可實現。

總結

本篇是對 module 的一個解讀。註冊 module 並無想象中的那麼複雜,主要分爲兩個步驟。

第一步是找到當前 module 的父 state,而後在其至少綁定當前 state 的監聽,保證修改了 state 會觸發相應。

第二步則是遞歸 module,保證設置子 module 的 state,從而實現 module 的子嵌套。

相關文章
相關標籤/搜索