上一篇文章說的是如何閱讀框架原始碼,收到了“如果更詳細一點就好了”的反饋,不如就以 Vuex 為切入點進行一次實踐吧,不矯揉不造作,說走我們就走~~
一、前提
本文假定你已經對 Vue 的使用上有一定的概念,不要求輕車熟路(使用過 Vuex 當然是最好的),但至少要了解基本的事件繫結方式,以及 Mixin 的用法,官方文件從此去
二、Vuex 解決了什麼問題
官方的說法:Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式
這裡首先要搞清楚什麼是狀態,狀態就是資料,也就是說: Vuex 提供了一套 Vue 應用統一的資料來源管理模式,除了定義資料來源,還定義了資料的管理模式
這其中,Store 所包含的兩個核心部分 State 和 Actions 分別代表了資料來源,和資料的管理(操作)模式,同時作為一個全域性的 VM,其有效的協調了 Vue 各元件間的通訊
三、Vuex 的設計思想
如果讀 Vue 文件的時候足夠留心,興許你能在外掛一節找到蛛絲馬跡:
外掛的功能包括,通過全域性 mixin 方法新增一些元件選項,如:vuex
也就是說,Vuex 不過是 Vue 的一個外掛,通過 Mixin 的方式給每個元件注入一個 $store 物件,由於每個元件的 $store 指向的是同一個 store 物件(後面通過詳讀程式碼可以知道,這個 $store 其實是一個 VM 物件),所以 store 是全域性的,這就印證了之前在我們為什麼需要 Vuex中的一個結論,Vuex 類似於一個事件匯流排
四、詳讀程式碼
通過 Mixin 注入 Store
從入口檔案 index.js 開始,程式碼不多,可以直接貼出來
export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions
}複製程式碼
如果你一眼就看出這裡的關鍵是 install,那麼你應該領略到讀原始碼先了解設計思想的獨特魅力了,沒錯,作為 Vue 的 Plugin,install 方法就是入口
循著 install 方法進入 store.js,還是符合預期,這個方法主要幹得是事情就是 mixin
export function install (_Vue) {
...
Vue = _Vue
applyMixin(Vue)
}
// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}複製程式碼
並且還有一個小細節,瀏覽器環境下並且 Vue 不為空的時候,引入 Vuex 之後是會自動註冊的
具體來看看 mixin.js 這個檔案,劃重點(注意看註釋):
// 通過鉤子 init / beforeCreate 執行 vuexInit
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
// 元件初始化的時候注入 $store
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
}
}複製程式碼
Store 物件
Vuex 的最佳實踐中,一般這樣使用(帶著目標去閱讀,效果更佳):
// create store
const store = new Vuex.Store({
actions: {
...
},
modules: {
...
}
})
import App from './comps/app.vue'
new Vue(Vue.util.extend({ el: '#root', store }, App))複製程式碼
我們需要新建一個 Store,在建立 Vue 例項的時候,作為引數傳入,在上一節的 vuexInit 函式中,是從 this.$options 中取出 store 賦值給元件的 $store 的,如此,便能無縫聯絡上了
接下來的重點,就是 Store 這個類了,還是 store.js 這個檔案,懷著入參為 ations 和 modules 的預期,來讀 constructor 方法,倒是有一個語句是用來處理 modules 的
this._modules = new ModuleCollection(options)複製程式碼
但真的是尋尋覓覓尋不到從 options 中取出 actions 進行處理的方法,當然後面仔細閱讀了 ModuleCollection 中的程式碼之後,才找到了答案,actions 引數也是在這裡面提取的。畢竟讓我糾結迷茫了良久,如果是我來寫的話,我可能不會這麼寫,方法的命名需要有語義性,而且一個方法也應當只做一件事情
原則上為了儘快理清主流程,有些細節需要暫時略過(所以語義化的命名、合理的函式拆分,對閱讀者來說是多麼的重要),假設已經知道前面的步驟已經從 options 中讀到了 actions 和 modules,那麼下一個核心節點就是:
installModule(this, state, [], this._modules.root)複製程式碼
這一步再進行分解(注意看註釋)
// 註冊 mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 註冊 action
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})
// 註冊 getter (computed)
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)
})複製程式碼
出於篇幅以及希望閱讀的同學親自實踐的目的,具體的註冊方式這裡不再展開
進入下一個重要環節 resetStoreVM,建立 VM,實現資料監聽(注意看註釋)
function resetStoreVM (store, state, hot) {
// bind store public getters
// getters 其實就是 computed
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// 建立一個 Vue 例項,作為 Store 的 VM
store._vm = new Vue({
data: {
$$state: state
},
computed
})
...
}複製程式碼
五、小結
至此,Vuex 的主流程程式碼基本上算是走了一遍,看似神奇,可是程式碼量並不大,還是那句話,希望閱讀的同學能夠按照這個套路自己走一遍
本文在公眾號菲麥前端同步發行: