Vuex是一個專爲Vue服務,用於管理頁面數據狀態、提供統一數據操做的生態系統。它集中於MVC模式中的Model層,規定全部的數據操做必須經過 action – mutation – state change 的流程來進行,再結合Vue的數據視圖雙向綁定特性來實現頁面的展現更新。統一的頁面狀態管理以及操做處理,可讓複雜的組件交互變得簡單清晰,同時可在調試模式下進行時光機般的倒退前進操做,查看數據改變過程,使code debug更加方便。vue
最近在開發的項目中用到了Vuex來管理總體頁面狀態,遇到了不少問題。決定研究下源碼,在答疑解惑以外,能深刻學習其實現原理。react
先將問題拋出來,使學習和研究更有針對性:git
注:本文對有Vuex有實際使用經驗的同窗幫助更大,能更清晰理解Vuex的工做流程和原理,使用起來更駕輕就熟。初次接觸的同窗,能夠先參考Vuex官方文檔進行基礎概念的學習。github
進行源碼分析以前,先了解一下官方文檔中提供的核心思想圖,它也表明着整個Vuex框架的運行流程。
如圖示,Vuex爲Vue Components創建起了一個完整的生態圈,包括開發中的API調用一環。圍繞這個生態圈,簡要介紹一下各模塊在覈心流程中的主要功能:vuex
Vue組件接收交互行爲,調用dispatch方法觸發action相關處理,若頁面狀態須要改變,則調用commit方法提交mutation修改state,經過getters獲取到state新值,從新渲染Vue Components,界面隨之更新。api
打開Vuex項目,看下源碼目錄結構。數組
Vuex提供了很是強大的狀態管理功能,源碼代碼量卻很少,目錄結構劃分也很清晰。先大致介紹下各個目錄文件的功能:promise
瞭解大概的目錄及對應功能後,下面開始進行源碼分析。index.js中包含了全部的核心代碼,從該文件入手進行分析。瀏覽器
先看個簡單的例子:緩存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* store.js文件
* 建立store對象,配置state、action、mutation以及getter
*
**/
import Vue from 'vue'
import Vuex from 'vuex'
// install Vuex框架
Vue.use(Vuex)
// 建立並導出store對象。爲了方便,不配置任何參數
export default new Vuex.Store()
|
store.js文件中,加載Vuex框架,建立並導出一個空配置的store對象實例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* vue-index.js文件
*
*
**/
import Vue from 'vue'
import App from './../pages/app.vue'
import store from './store.js'
new Vue({
el: '#root',
router,
store,
render: h => h(App)
})
|
而後在index.js中,正常初始化一個頁面根級別的Vue組件,傳入這個自定義的store對象。
如問題1所述,以上實例除了Vue的初始化代碼,只是多了一個store對象的傳入。一塊兒看下源碼中的實現方式。
index.js文件代碼執行開頭,定義局部 Vue 變量,用於判斷是否已經裝載和減小全局做用域查找。
1
|
let Vue
|
而後判斷若處於瀏覽器環境下且加載過Vue,則執行install方法。
1
2
3
4
|
// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
|
install方法將Vuex裝載到Vue對象上,Vue.use(Vuex) 也是經過它執行,先看下Vue.use方法實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function (plugin: Function | Object) {
/* istanbul ignore if */
if (plugin.installed) {
return
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
// 實際執行插件的install方法
plugin.install.apply(plugin, args)
} else {
plugin.apply(null, args)
}
plugin.installed = true
return this
}
|
如果首次加載,將局部Vue變量賦值爲全局的Vue對象,並執行applyMixin方法,install實現以下:
1
2
3
4
5
6
7
8
9
10
|
function install (_Vue) {
if (Vue) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
return
}
Vue = _Vue
applyMixin(Vue)
}
|
來看下applyMixin方法內部代碼。若是是2.x.x以上版本,可使用 hook 的形式進行注入,或使用封裝並替換Vue對象原型的_init方法,實現注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
|
具體實現:將初始化Vue根組件時傳入的store設置到this對象的$store屬性上,子組件從其父組件引用$store屬性,層層嵌套進行設置。在任意組件中執行 this.$store 都能找到裝載的那個store對象,vuexInit方法實現以下:
1
2
3
4
5
6
7
8
9
|
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的傳遞。
頁面Vue結構圖:
對應store流向:
上面對Vuex框架的裝載以及注入自定義store對象進行分析,解決了問題1。接下來詳細分析store對象的內部功能和具體實現,來解答 爲何actions、getters、mutations中能從arguments[0]中拿到store的相關數據? 等問題。
store對象實現邏輯比較複雜,先看下構造方法的總體邏輯流程來幫助後面的理解:
開始分析store的構造函數,分小節逐函數逐行的分析其功能。
1
2
3
|
constructor (options = {}) {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
|
在store構造函數中執行環境判斷,如下都是Vuex工做的必要條件:
assert函數是一個簡單的斷言函數的實現,一行代碼便可實現。
1
2
3
|
function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
|
環境判斷後,根據new構造傳入的options或默認值,初始化內部數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
const {
state = {},
plugins = [],
strict = false
} = options
// store internal state
this._committing = false // 是否在進行提交狀態標識
this._actions = Object.create(null) // acitons操做對象
this._mutations = Object.create(null) // mutations操做對象
this._wrappedGetters = Object.create(null) // 封裝後的getters集合對象
this._modules = new ModuleCollection(options) // Vuex支持store分模塊傳入,存儲分析後的modules
this._modulesNamespaceMap = Object.create(null) // 模塊命名空間map
this._subscribers = [] // 訂閱函數集合,Vuex提供了subscribe功能
this._watcherVM = new Vue() // Vue組件用於watch監視變化
|
調用 new Vuex.store(options) 時傳入的options對象,用於構造ModuleCollection類,下面看看其功能。
1
2
3
4
5
6
7
8
9
10
|
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.root = new Module(rawRootModule, false)
// register all nested modules
if (rawRootModule.modules) {
forEachValue(rawRootModule.modules, (rawModule, key) => {
this.register([key], rawModule, false)
})
}
|
ModuleCollection主要將傳入的options對象整個構造爲一個module對象,並循環調用 this.register([key], rawModule, false) 爲其中的modules屬性進行模塊註冊,使其都成爲module對象,最後options對象被構形成一個完整的組件樹。ModuleCollection類還提供了modules的更替功能,詳細實現能夠查看源文件module-collection.js。
繼續回到store的構造函數代碼。
1
2
3
4
5
6
7
8
9
10
11
|
// bind commit and dispatch to self
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)
}
|
封裝替換原型中的dispatch和commit方法,將this指向當前store對象。dispatch和commit方法具體實現以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 配置參數處理
// 當前type下全部action處理函數集合
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
|
前面提到,dispatch的功能是觸發並傳遞一些參數(payload)給對應type的action。由於其支持2種調用方法,因此在dispatch中,先進行參數的適配處理,而後判斷action type是否存在,若存在就逐個執行(注:上面代碼中的this._actions[type] 以及 下面的 this._mutations[type] 均是處理過的函數集合,具體內容留到後面進行分析)。
commit方法和dispatch相比雖然都是觸發type,可是對應的處理卻相對複雜,代碼以下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
if (!entry) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
// 專用修改state方法,其餘修改state方法均是非法修改
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 訂閱者函數遍歷執行,傳入當前的mutation對象和當前的state
this._subscribers.forEach(sub => sub(mutation, this.state))
if (options && options.silent) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
|
該方法一樣支持2種調用方法。先進行參數適配,判斷觸發mutation type,利用_withCommit方法執行本次批量觸發mutation處理函數,並傳入payload參數。執行完成後,通知全部_subscribers(訂閱函數)本次操做的mutation對象以及當前的state狀態,若是傳入了已經移除的silent選項則進行提示警告。
_withCommit是一個代理方法,全部觸發mutation的進行state修改的操做都通過它,由此來統一管理監控state狀態的修改。實現代碼以下。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
_withCommit (fn) {
// 保存以前的提交狀態
const committing = this._committing
// 進行本次提交,若不設置爲true,直接修改state,strict模式下,Vuex將會產生非法修改state的警告
this._committing = true
// 執行state的修改操做
fn()
// 修改完成,還本來次修改以前的狀態
this._committing = committing
}
|
緩存執行時的committing狀態將當前狀態設置爲true後進行本次提交操做,待操做完畢後,將committing狀態還原爲以前的狀態。
綁定dispatch和commit方法以後,進行嚴格模式的設置,以及模塊的安裝(installModule)。因爲佔用資源較多影響頁面性能,嚴格模式建議只在開發模式開啓,上線後須要關閉。
1
2
3
4
5
6
7
|
// strict mode
this.strict = strict
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
|
上述代碼的備註中,提到installModule方法初始化組件樹根組件、註冊全部子組件,並將其中全部的getters存儲到this._wrappedGetters屬性中,讓咱們看看其中的代碼實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (namespace) {
store._modulesNamespaceMap[namespace] = module
}
// 非根組件設置 state 方法
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
······
|
判斷是不是根目錄,以及是否設置了命名空間,若存在則在namespace中進行module的存儲,在不是根組件且不是 hot 條件的狀況下,經過getNestedState方法拿到該module父級的state,拿到其所在的 moduleName ,調用 Vue.set(parentState, moduleName, module.state) 方法將其state設置到父級state對象的moduleName屬性中,由此實現該模塊的state註冊(首次執行這裏,由於是根目錄註冊,因此並不會執行該條件中的方法)。getNestedState方法代碼很簡單,分析path拿到state,以下。
1
2
3
4
5
|
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
|
1
|
const local = module.context = makeLocalContext(store, namespace, path)
|
命名空間和根目錄條件判斷完畢後,接下來定義local變量和module.context的值,執行makeLocalContext方法,爲該module設置局部的 dispatch、commit方法以及getters和state(因爲namespace的存在須要作兼容處理)。
定義local環境後,循環註冊咱們在options中配置的action以及mutation等。逐個分析各註冊函數以前,先看下模塊間的邏輯關係流程圖:
下面分析代碼邏輯:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 註冊對應模塊的mutation,供state修改使用
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 註冊對應模塊的action,供數據操做、提交mutation等異步操做使用
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})
// 註冊對應模塊的getters,供state讀取使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
|
registerMutation方法中,獲取store中的對應mutation type的處理函數集合,將新的處理函數push進去。這裏將咱們設置在mutations type上對應的 handler 進行了封裝,給原函數傳入了state。在執行 commit(‘xxx’, payload) 的時候,type爲 xxx 的mutation的全部handler都會接收到state以及payload,這就是在handler裏面拿到state的緣由。
1
2
3
4
5
6
7
8
9
|
function registerMutation (store, type, handler, local) {
// 取出對應type的mutations-handler集合
const entry = store._mutations[type] || (store._mutations[type] = [])
// commit實際調用的不是咱們傳入的handler,而是通過封裝的
entry.push(function wrappedMutationHandler (payload) {
// 調用handler並將state傳入
handler(local.state, payload)
})
}
|
action和getter的註冊也是同理的,看一下代碼(注:前面提到的 this.actions 以及 this.mutations在此處進行設置)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
function registerAction (store, type, handler, local) {
// 取出對應type的actions-handler集合
const entry = store._actions[type] || (store._actions[type] = [])
// 存儲新的封裝過的action-handler
entry.push(function wrappedActionHandler (payload, cb) {
// 傳入 state 等對象供咱們原action-handler使用
let res = handler({
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
// action須要支持promise進行鏈式調用,這裏進行兼容處理
if (!isPromise(res)) {
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
function registerGetter (store, type, rawGetter, local) {
// getters只容許存在一個處理函數,若重複須要報錯
if (store._wrappedGetters[type]) {
console.error(`[vuex] duplicate getter key: ${type}`)
return
}
// 存儲封裝過的getters處理函數
store._wrappedGetters[type] = function wrappedGetter (store) {
// 爲原getters傳入對應狀態
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
|
action handler比mutation handler以及getter wrapper多拿到dispatch和commit操做方法,所以action能夠進行dispatch action和commit mutation操做。
註冊完了根組件的actions、mutations以及getters後,遞歸調用自身,爲子組件註冊其state,actions、mutations以及getters等。
1
2
3
|
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
|
前面介紹了dispatch和commit方法以及actions等的實現,下面結合一個官方的購物車實例中的部分代碼來加深理解。
Vuex配置代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/
* store-index.js store配置文件
*
/
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import cart from './modules/cart'
import products from './modules/products'
import createLogger from '../../../src/plugins/logger'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
actions,
getters,
modules: {
cart,
products
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
|
Vuex組件module中各模塊state配置代碼部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* cart.js
*
**/
const state = {
added: [],
checkoutStatus: null
}
/**
* products.js
*
**/
const state = {
all: []
}
|
加載上述配置後,頁面state結構以下圖:
state中的屬性配置都是按照option配置中module path的規則來進行的,下面看action的操做實例。
Vuecart組件代碼部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* Cart.vue 省略template代碼,只看script部分
*
**/
export default {
methods: {
// 購物車中的購買按鈕,點擊後會觸發結算。源碼中會調用 dispatch方法
checkout (products) {
this.$store.dispatch('checkout', products)
}
}
}
|
Vuexcart.js組件action配置代碼部分:
1
2
3
4
5
6
7
8
9
10
11
|
const actions = {
checkout ({ commit, state }, products) {
const savedCartItems = [...state.added] // 存儲添加到購物車的商品
commit(types.CHECKOUT_REQUEST) // 設置提交結算狀態
shop.buyProducts( // 提交api請求,並傳入成功與失敗的cb-func
products,
() => commit(types.CHECKOUT_SUCCESS), // 請求返回成功則設置提交成功狀態
() => commit(types.CHECKOUT_FAILURE, { savedCartItems }) // 請求返回失敗則設置提交失敗狀態
)
}
}
|
Vue組件中點擊購買執行當前module的dispatch方法,傳入type值爲 ‘checkout’,payload值爲 ‘products’,在源碼中dispatch方法在全部註冊過的actions中查找’checkout’的對應執行數組,取出循環執行。執行的是被封裝過的被命名爲wrappedActionHandler的方法,真正傳入的checkout的執行函數在wrappedActionHandler這個方法中被執行,源碼以下(注:前面貼過,這裏再看一次):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function wrappedActionHandler (payload, cb) {
let res = handler({
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
}
|
handler在這裏就是傳入的checkout函數,其執行須要的commit以及state就是在這裏被傳入,payload也傳入了,在實例中對應接收的參數名爲products。commit的執行也是同理的,實例中checkout還進行了一次commit操做,提交一次type值爲types.CHECKOUT_REQUEST的修改,由於mutation名字是惟一的,這裏進行了常量形式的調用,防止命名重複,執行跟源碼分析中一致,調用 function wrappedMutationHandler (payload) { handler(local.state, payload) } 封裝函數來實際調用配置的mutation方法。
看到完源碼分析和上面的小實例,應該能理解dispatch action和commit mutation的工做原理了。接着看源碼,看看getters是如何實現state實時訪問的。
執行完各module的install後,執行resetStoreVM方法,進行store組件的初始化。
1
2
3
|
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
|
綜合前面的分析能夠了解到,Vuex其實構建的就是一個名爲store的vm組件,全部配置的state、actions、mutations以及getters都是其組件的屬性,全部的操做都是對這個vm組件進行的。
一塊兒看下resetStoreVM方法的內部實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
function resetStoreVM (store, state) {
const oldVm = store._vm // 緩存前vm組件
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// 循環全部處理過的getters,並新建computed對象進行存儲,經過Object.defineProperty方法爲getters對象創建屬性,使得咱們經過this.$store.getters.xxxgetter可以訪問到該getters
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
})
})
// 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
// 設置新的storeVm,將當前初始化的state以及getters做爲computed屬性(剛剛遍歷生成的)
store._vm = new Vue({
data: { state },
computed
})
// 恢復Vue的模式
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
// 該方法對state執行$watch以禁止從mutation外部修改state
enableStrictMode(store)
}
// 若不是初始化過程執行的該方法,將舊的組件state設置爲null,強制更新全部監聽者(watchers),待更新生效,DOM更新完成後,執行vm組件的destroy方法進行銷燬,減小內存的佔用
if (oldVm) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation.
store._withCommit(() => {
oldVm.state = null
})
Vue.nextTick(() => oldVm.$destroy())
}
}
|
resetStoreVm方法建立了當前store實例的_vm組件,至此store就建立完畢了。上面代碼涉及到了嚴格模式的判斷,看一下嚴格模式如何實現的。
1
2
3
4
5
|
function enableStrictMode (store) {
store._vm.$watch('state', () => {
assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
}, { deep: true, sync: true })
}
|
很簡單的應用,監視state的變化,若是沒有經過 this._withCommit() 方法進行state修改,則報錯。
最後執行plugin的植入。
1
|
plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
|
devtoolPlugin提供的功能有3個:
1
2
3
4
5
6
7
8
9
10
11
12
|
// 1. 觸發Vuex組件初始化的hook
devtoolHook.emit('vuex:init', store)
// 2. 提供「時空穿梭」功能,即state操做的前進和倒退
devtoolHook.on('vuex:travel-to-state', targetState => {
store.replaceState(targetState)
})
// 3. mutation被執行時,觸發hook,並提供被觸發的mutation函數和當前的state狀態
store.subscribe((mutation, state) => {
devtoolHook.emit('vuex:mutation', mutation, state)
})
|
源碼分析到這裏,Vuex框架的實現原理基本都已經分析完畢。
最後咱們回過來看文章開始提出的5個問題。
1. 問:使用Vuex只需執行 Vue.use(Vuex),並在Vue的配置中傳入一個store對象的示例,store是如何實現注入的?
答:Vue.use(Vuex) 方法執行的是install方法,它實現了Vue實例對象的init方法封裝和注入,使傳入的store對象被設置到Vue上下文環境的$store中。所以在Vue Component任意地方都可以經過this.$store訪問到該store。
2. 問:state內部支持模塊配置和模塊嵌套,如何實現的?
答:在store構造方法中有makeLocalContext方法,全部module都會有一個local context,根據配置時的path進行匹配。因此執行如dispatch(‘submitOrder’, payload)這類action時,默認的拿到都是module的local state,若是要訪問最外層或者是其餘module的state,只能從rootState按照path路徑逐步進行訪問。
3. 問:在執行dispatch觸發action(commit同理)的時候,只需傳入(type, payload),action執行函數中第一個參數store從哪裏獲取的?
答:store初始化時,全部配置的action和mutation以及getters均被封裝過。在執行如dispatch(‘submitOrder’, payload)的時候,actions中type爲submitOrder的全部處理方法都是被封裝後的,其第一個參數爲當前的store對象,因此可以獲取到 { dispatch, commit, state, rootState } 等數據。
4. 問:Vuex如何區分state是外部直接修改,仍是經過mutation方法修改的?
答:Vuex中修改state的惟一渠道就是執行 commit(‘xx’, payload) 方法,其底層經過執行 this._withCommit(fn) 設置_committing標誌變量爲true,而後才能修改state,修改完畢還須要還原_committing變量。外部修改雖然可以直接修改state,可是並無修改_committing標誌位,因此只要watch一下state,state change時判斷是否_committing值爲true,便可判斷修改的合法性。
5. 問:調試時的」時空穿梭」功能是如何實現的?
答:devtoolPlugin中提供了此功能。由於dev模式下全部的state change都會被記錄下來,’時空穿梭’ 功能其實就是將當前的state替換爲記錄中某個時刻的state狀態,利用 store.replaceState(targetState) 方法將執行this._vm.state = state 實現。
源碼中還有一些工具函數相似registerModule、unregisterModule、hotUpdate、watch以及subscribe等,若有興趣能夠打開源碼看看,這裏再也不細述。