書接上回,上回書說了 vuex 的安裝、以及 store
建構函式,下面我們來講後面部分
收集 modules
vuex 允許我們自定義多個模組,防止應用的所有狀態會集中到一個比較大的物件,導致 store 就變的臃腫了。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
複製程式碼
這是如何實現的呢?
在store的建構函式裡面有這樣一段程式碼:this._modules = new ModuleCollection(options)
,他就是用來收集使用者自定義的 modules
,該函式位於 module/module-collection.js
下
現在假設我們的 modules 做如下設定:
store = {
modules: {
moduleA: {
state: {},
modules: {
moduleC: {
state: {},
actions: {}
}
}
},
modulesB: {
state: {},
mutations: {
// xxx
}
}
}
}
複製程式碼
模組關係圖如下:
來到 ModuleCollection
的建構函式,很簡單,呼叫其 register
方法,並傳入三個引數
register(path, rawModule, runtime = true) {
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
// 整個傳入store 裡的 option 為一個大的 module ,
// 再對option 裡的 modules 屬性進行遞迴註冊為 module
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
// 這裡的 concat 並不改變原來的陣列,所以如果是同級的 module ,那麼他還是有著相同的父級
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
複製程式碼
現在我們一步一步來分析。
先講一下 register
裡的對傳入模組結構的斷言,呼叫 assertRawModule(path, rawModule)
以確保 getters/mutations/actions
依次符合 函式/函式/(物件或者函式)
的結構,這部分程式碼並不難,但是作者的這種編碼習慣非常值得學習,詳見 assertRawModule
回到正題
第一次 呼叫 this.register([], rawRootModule, false)
此時傳入 register
函式的 path
為 空陣列
,rawModule 為最外層的 store
物件,即可以理解為 根module
,runtime 為 false
接著呼叫 new Module(rawModule, runtime)
例項化這個 根module
constructor (rawModule, runtime) {
this.runtime = runtime
// 儲存子 modules
this._children = Object.create(null)
// Store the origin module object which passed by programmer
// 儲存原始的這個傳進來的 module
this._rawModule = rawModule
const rawState = rawModule.state // 獲取模組的 state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state 最終是一個物件
}
複製程式碼
回到 register
函式,如果此時的 path 為 空陣列,那麼就將此模組設定為 整個狀態樹的 根模組
,即this.root= 根 module
,這裡我們的 path=[]
,所以它是 根 module
,不走 else
來到後面,判斷該模組是否有 modules
屬性,即有子模組,有則繼續迴圈註冊子模組,我們這裡有 moduleA
和 moduleB
,所以繼續註冊
// util.js
function forEachValue (obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key))
}
複製程式碼
第二次呼叫 this.register(path.concat(key), rawChildModule, runtime)
此時傳入 register
函式的 path
為 path.concat(key)
,即 path =['moduleA']
,rawModule 為 moduleA
物件,runtime 為 false
注:
path.concat(key)
並不改變原始的path
,它返回一個新的陣列,所以根module的path棧
還是空,這一點很重要
繼續重複第一步的步驟,不同的是,例項化完 moduleA
後,由於此時的 path =['moduleA']
,所以它走 else
else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
複製程式碼
path.slice(0, -1)
返回 path 陣列以外的其他元素,不改變原始陣列,所以等價於 this.get([])
// 作用:獲取當前的模組的父模組
// 傳入的 path 是從根模組到父模組的一條鏈路
get(path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
複製程式碼
this.root
為前面的 根 module
,而 path 是空,所以 parent=根 module
,然後執行 parent.addChild(path[path.length - 1], newModule)
,此時獲取 path 棧頂元素("moduleA"
)作為 key ,和 例項 moduleA
作為 value ,加入到 父模組(根 module)的子元素物件中
由於 moduleA
還有子模組,所以繼續遞迴 子模組
第三次呼叫 this.register(path.concat(key), rawChildModule, runtime)
此時傳入 register
函式的 path
為 path.concat(key)
,即 path =['moduleA']
,rawModule 為 moduleA
物件,runtime 為 false
繼續上面步驟
來到 this.get
,這是傳入的引數是 ['moduleA']
,即 moduleC
的父模組 moduleA
。由於 根module
儲存了 moduleA
,所以通過這種類似於鏈的方式來獲取 父模組
,同理將 moduleC
加入 moduleA
的子模組物件中
至此,第一條鏈就講完了,
返回到 根module
的 forEachValue
迴圈中,這裡我們講到,他的 path 還是空
,這就體現了 使用 concat
方法的好處與機智。 所以與處理 moduleA
的過程一模一樣
第四次呼叫 this.register(path.concat(key), rawChildModule, runtime)
此時傳入 register
函式的 path
為 path.concat(key)
,即 path =['moduleB']
,rawModule 為 moduleB
物件,runtime 為 false
終於將 this._modules = new ModuleCollection(options)
的過程分析完畢了
最終的 this._modules.root(不包括方法)
如下圖所示
總的看下來挺佩服作者的思維以及處理方式的
看著挺長的了,其實就是多了幾個迴圈過程的講解,所以要不要再翻篇呢?呢?呢?????
回到 store.js 的建構函式
,
const state = this._modules.root.state // 將 "根模組物件的 state" (即最外層store的state物件)賦予 state ,
複製程式碼
installModule(this, state, [], this._modules.root)
初始化根模組,並且遞迴註冊子模組,並且收集所有模組的 getters
function installModule(store, rootState, path, module, hot) {
// hot 當動態改變 modules 或者熱更新的時候為 true.
const isRoot = !path.length // 判斷是否是根模組
const namespace = store._modules.getNamespace(path) // 獲取s使用的名稱空間
// register in namespace map
if (module.namespaced) {
// 如果名稱空間存在,就在store 物件中建立 namespace 到模組的對映
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) { // 如果不是根模組以及 hot = false,這裡我們是根模組,所以我們先放一放,跳到下一步
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
// 跳到下面先看看makeLocalContext,
// 又跳!!!放心,,不用跳多遠,也就是下一個 issue,,,摳鼻.gif
const local = module.context = makeLocalContext(store, namespace, path)
// 註冊各個模組的 mutaations 方法到 store._mutations 中,每個type對應一個陣列
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 註冊各個模組的 actions 到store._actions
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 註冊各個模組的 getters 到store._wrappedGetters
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
module.forEachChild((child, key) => { // 遞迴子模組
installModule(store, rootState, path.concat(key), child, hot)
})
}
複製程式碼
上面出現的有關函式由於排版以及篇幅原因,我放到了 我看Vue(三) 中。
總結下,installModule 都幹了些神馬:
- 獲取名稱空間
- 獲取本模組的 context 屬性,裡面包含了本模組的
dispatch/commit/getter/state
等屬性或方法 - 將 各個模組按照名稱空間將
mutations/getters/actions
加入到全域性的_mutations /_wrappedGetters/_actions
裡
接下來我們簡單講講 在元件裡面呼叫 dispatch、commit
的過程
dispatch/commit
commit(_type, _payload, _options) {
// check object-style commit
// 這裡就是獲取正確的 type / payload /options
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = {
type,
payload
}
// 獲取觸發的type 對應的 mutation
const entry = this._mutations[type]
if (!entry) { // 如果不存在,給出警告
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => { // 由於entry是一個陣列,所以逐個執行,並傳入負載
entry.forEach(function commitIterator(handler) {
handler(payload)
})
})
// 觸發 訂閱函式
this._subscribers.forEach(sub => sub(mutation, this.state))
if (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
dispatch(_type, _payload) { // 基本和commit 一樣
// xxx
}
複製程式碼
看完上面的程式碼,我有些疑問,這個 store.state
和 store.getter
是哪來的?
resetStoreVM(this, state)
function resetStoreVM(store, state, hot) {
const oldVm = store._vm // 之前的 vue 例項
// bind store public getters
store.getters = {} // 終於找到你
const wrappedGetters = store._wrappedGetters // 前面說過的各個模組的 getters 集合
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store) // 收集各個 getter,等會傳入 computed ,以此做到響應式
Object.defineProperty(store.getters, key, {
get: () => store._vm[key], // 因為利用了計算屬性,所以各個 getter 就變成了 vue 例項的屬性
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent //// 是否取消Vue 所有的日誌與警告
Vue.config.silent = true
// 重新 new
store._vm = new Vue({
data: {
$$state: state // 這裡雖然是 $$state,但是利用store.state時獲取的就是它
},
computed
})
/* get state() {
return this._vm._data.$$state
}*/
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
if (hot) {
// 解除舊vm的state的引用,以及銷燬舊的Vue物件
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
複製程式碼
解決了 store.getter了,那麼 store.state是如何來的呢?
還記不記得第一次讓你跳的地方,沒錯就是 installModule
if (!isRoot && !hot) { // 如果不是根模組以及 hot = false,這裡我們是根模組,所以我們先放一放,跳到下一步
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
複製程式碼
這裡我們就來看 Vue.set(parentState, moduleName, module.state)
。 它的作用是在父模組的state 屬性上新增上本模組的state,還是按照一開始我們那種依賴關係來看:
這樣我們就不難理解 getNestState
裡面為什麼可以如此獲取 state
了
好了,vuex
核心大致的內容就是這些,後面在 我看Vuex(三)
中我會解釋下其他一些函式的作用
原文地址: 我看Vuex(二)
如有不當,歡迎指出交流,謝謝 ^_^