store 將應用的狀態集中起來,但若是應用變得很是複雜時,即狀態很是的多時,store 就有可能變得至關臃腫。module 可以幫 store 劃分了模塊,每一個模塊都擁有本身的 state、getter、mutation、action 和 module。vue
那麼 module 又是怎樣進行劃分的,劃分後的模塊又是如何管理本身的狀態呢?接下來就來解讀 module 的實現吧。vuex
解讀前,須要對如下知識有所瞭解:api
在 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.count
。ui
可是示例中的 mutation 則是註冊在全局下的,即調用 store.commit('addNote')
,將會調用跟模塊和 a 模塊的 mutation。除非區分各模塊 mutation 的命名,不然,在同名的狀況下,只要 commit 後就會被觸發調用。spa
固然,vuex 2.0.0 後面的版本添加了命名空間
的功能,使得 module 更加的模塊化。prototype
因此接下來要解讀的 module 中,實際上只要 state 是被模塊化了, action、mutation 和 getter 仍是在全局的模塊下。code
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。
因此 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 實例可以監聽到添加屬性的改動。
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。
以前在解讀 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 的子嵌套。