之前閱讀過 redux
的原始碼,vuex
同樣也是一個狀態管理工具,之前開發 vue 應用的時候,用到的 vuex 也比較多,所以自然對 vuex 頗有興趣,最主要的原因是 我愛學習
,(不好意思,臉掉地上了),,哈哈哈哈哈哈
(其實過年這幾天也挺無聊的。。。
接下來我們來回歸正題
Vuex 是如何引入到 Vue 中的
我們都知道,在 Vue 中使用 Vuex 必須通過 Vue.use(Vuex) 方法來使用,所以先來瞄一瞄 Vue 對 Vuex 做了什麼不可告人的事,驚恐.gif
來到 vue 原始碼下的 core/global-api/use.js
Vue.use = function (plugin: Function | Object) {
// 檢測該外掛是否已經被安裝
if (plugin.installed) {
return
}
// use 支援傳入一個選項物件,如
// Vue.use(MyPlugin, { someOption: true })
const args = toArray(arguments, 1) //獲取可選物件
args.unshift(this) // 將Vue 構造器傳入,當外掛的第一個引數
if (typeof plugin.install === 'function') {
// install執行外掛安裝
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
plugin.installed = true // 防止再次安裝
return this
}
複製程式碼
所以,vue 對外掛還是挺溫柔的,只用了它的一個安裝函式進行外掛的安裝。好,我們來看看 vuex 的 install
函式
在 store.js 的最下面,我們看到了它的身影
function install(_Vue) {
if (Vue && _Vue === Vue) {
// 給出警告:說明重複安裝
return
}
Vue = _Vue
applyMixin(Vue)
}
複製程式碼
這裡我們也可以看到 vuex 自己內部也做了一個限制,防止重複安裝,最後該函式呼叫了 applyMixin(Vue)
咻一下,我們來到 mixin.js
,該函式也很簡單,先是獲取 Vue
的版本號,如果版本是 2.0 以上版本,就使用 Vue.mixin
來在所有元件中混入 beforeCreate
生命鉤子,對應的處理函式是 vuexInit
,反之就向後相容 1.x
版本
function vuexInit() {
// this -> vm
const options = this.$options
// store injection
if (options.store) { // 獲取傳入new Vue({store}) 裡面的 store,並註冊為 vm 的 $store 屬性,這樣我們就能在例項中使用 this.$store 訪問 store 物件了
this.$store = typeof options.store === 'function' ?
options.store() :
options.store
} else if (options.parent && options.parent.$store) {
// 子元件從其父元件引用 $store 屬性
this.$store = options.parent.$store
}
}
複製程式碼
至此,前菜已經上齊了
主角 Store 閃亮登場
store.js
,別看長長一竄,有500多行,其實看進去了,你會感覺,也沒啥可怕嘛
該檔案主要由一個 Store
類和一些輔助函式構成,我們先來看 這個構造類
該建構函式中首先是進行一些必要的判斷,如瀏覽器環境下自動安裝、是否已經安裝了 Vue、是否支援 Promise
,是否已經例項化了 Store 建構函式,其中用到了 assert
斷言函式
// 建構函式
constructor(options = {}) {
// .......
}
// util.js
function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
複製程式碼
然後是從傳入的options 中提取出 plugins
和 strict
,並做一些變數的初始化
// store internal state
// 表示提交狀態,作用是保證對 Vuex 中 state 的修改只能在 mutation 的回撥函式中,而不能在外部隨意修改state
this._committing = false
// 使用者定義的 actions
this._actions = Object.create(null)
// action 訂閱者
this._actionSubscribers = []
// // 使用者定義的 mutations
this._mutations = Object.create(null)
// 使用者定義的 getters
this._wrappedGetters = Object.create(null)
// 收集使用者定義的 modules
this._modules = new ModuleCollection(options)
// 模組名稱空間map
this._modulesNamespaceMap = Object.create(null)
// 儲存所有對 mutation 變化的訂閱者,當執行commit時會執行佇列中的函式
this._subscribers = []
// 建立一個 Vue 例項, 利用 $watch 監測 store 資料的變化
this._watcherVM = new Vue()
複製程式碼
接著後面是對 dispatch 和 commit 函式中的 this 的重新繫結
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)
}
複製程式碼
這樣做是因為在元件中通過 this.$store 直接呼叫 dispatch/commit 方法時, 能夠使 dispatch/commit 方法中的 this 指向當前的 store 物件而不是當前元件的 this。
我們知道 new Vue
時會把傳入的物件的中的 this
繫結為 vm
,例如 computed
屬性,裡面我們寫如下程式碼時,會把計算屬性裡面的 this 繫結為當前的 vm 例項
computed: {
// 計算屬性的 getter
reversedMessage: function () {
// `this` 指向 vm 例項
return this.message.split('').reverse().join('')
}
}
複製程式碼
( 上面這段如有不妥,歡迎指出,謝謝 ^_^
接著就是 安裝模組,vm 元件設定,傳入外掛以及 devtoolPlugin
外掛的設定了
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
if (Vue.config.devtools) {
devtoolPlugin(this)
}
複製程式碼
接下來我們先不講 dispatch
和 commit
是怎麼實現的,先來重點關注下 modules
這部分,畢竟前面已經 new ModuleCollection(options)
,先來後到嘛,,哈哈
由於現在的篇幅也算可以了,怕大家看到長篇大論頭疼,所以我們轉移陣地。
(如有不當,歡迎指出,謝謝 ^_^
原文地址: 我看Vuex(一)