之前看了一下Vuex源碼,在這裏分享一下。
準備階段:
先去github上把源碼下載下來:
git clone https://github.com/vuejs/vuex.git
cd vuex
npm i
把依賴安裝一下,這樣就可以運行 npm run dev
之後在瀏覽器上打開http://localhost:8080/ ,就可以看到如下頁面:
隨便點擊進入一個項目,之後,在src目錄下做修改,比如打log,就會立刻在頁面上體現出來,這樣就比較方便我們瞭解各個變量具體是什麼。這也是我閱讀源碼的方法,不知道大家是如何閱讀源碼的呢。
正式開始
Vuex也是個vue插件,所以入口肯定是install,代碼很簡單:
export function install(_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
這裏主要是做了一個限制,只能調用一次Vue.use(Vuex),接着就調用applyMixin函數,
這個函數也比較簡單:
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({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)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit() {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
這裏做了一層兼容,兼容vue2.0之前的版本,主要是把vuexInit註冊到vue的生命週期中。vuexInit做的事情就是把store綁定到vue上,所以我們平常寫代碼可以使用this. store就是我們實例化Vue的時候傳進去的store。
那這個store又是什麼呢?平常我們的代碼都是這樣寫的:
let store = new Vuex.Store({
state,
getters,
actions,
mutations,
plugins: process.env.NODE_ENV !== 'production'
? [createLogger()]
: []
})
new Vue({
el: '#app',
store,
render: h => h(App)
})
可以看到,這個store就是Vuex.Store,所以,這個Vuex.Store就是Vuex的核心。接下來重點分析一下這個Store到底是怎麼運作的。
先看一下Store類的構造函數:(下面的代碼包含我的註釋,通過看這些註釋,可以更好的理解)
constructor(options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `store must be called with the new operator.`)
}
const {
plugins = [],
strict = false
} = options
// store internal state
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// 這裏需要關注一下
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
// 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)
}
// strict mode
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)
}
}
這裏的代碼邏輯比較清晰,一開始是對一些成員變量做初始化,這裏比較需要關注的是:
this._modules = new ModuleCollection(options)
ModuleCollection的代碼在module-collection.js中,ModuleCollection類主要是把配置信息註冊到他的成員變量root中,root是Module類的實例,用於配置信息。
最重要的就是這兩行代碼:
// 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)
installModule的邏輯就是把配置中的getters、actions、mutations註冊到Store中的_mutations、_actions、_wrappedGetters中。代碼如下:
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = 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, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
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)
})
}
這裏的module就是Store._modules.root,是Module類的實例,所以,module.forEachMutation就是遍歷new Vuex.Store時傳進去的mutations對象,然後把處理函數放到_mutations中,代碼如下:
module.forEachMutation((mutation, key) => {
// 這裏namespace先不去管它
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload)
})
}
看到這行代碼:
handler.call(store, local.state, payload)
再結合我們平常定義mutations:
mutations: {
increment (state) {
// 變更狀態
state.count++
}
}
可以看到,我們mutations中的state就是這個local.state,
之後是action的註冊,註冊action的代碼如下:
function registerAction(store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler(payload, cb) {
let res = handler.call(store, {
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
}
})
}
從上面我們可以看到,我們平常定義action的時候,第一個參數對象可以使用dispatch、commit等。這裏需要注意的是:
if (!isPromise(res)) {
res = Promise.resolve(res)
}
這裏會把handle調用的結果改成Promise,所以我們可以使用:
store.dispatch('actionA').then(() => {
// ...
})
最後是getter的註冊,跟上面action差不多,這裏就不再敖述。
接下來我們再看一下resetStoreVM函數。再看resetStoreVM之前,我們先看構造函數下方的這段代碼:
get state() {
return this._vm._data.$$state
}
set state(v) {
if (process.env.NODE_ENV !== 'production') {
assert(false, `use store.replaceState() to explicit replace store state.`)
}
}
看到這裏我們知道,我們平常使用
return this.$store.state.xxx
原來使用的Store類中的_vm._data.$$state,那這個變量又是什麼呢?看看resetStoreVM我們就知道了。
// 這裏的state是_modules.root.state,這個也就是配置時的state
function resetStoreVM(store, state, hot) {
const oldVm = store._vm
// bind store public getters
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
})
})
// 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.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
if (hot) {
// 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._vm = new Vue({
data: {
$$state: state
},
computed
})
```
看到這裏,我們知道,原來_vm就是一個Vue實例,data中的$$state就是我們配置時的state。這裏有一點需要注意,vue實例會把構造函數參數中的data放到_data裏面,所以使用_vm._data.$$state可以訪問。在這裏,還有一段有意思的代碼:
<div class="se-preview-section-delimiter"></div>
```javascript
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
})
})
在之前我們已經知道,_wrappedGetters裏面存放的就是getter中的配置,所以,這裏就是把_wrappedGetters中的屬性映射到store.getter上,但是它返回的是store._vm中的屬性,store._vm中的屬性就是把computed當做計算屬性。
到這裏,我們知道,Store類的構造函數主要就是處理配置信息,其中state放到_modules.root.state中,mutations放在_mutations中,actions放到_actions中,getters放到_wrappedGetters。我們平常用的this. $state就是配置時的state,還有我們用的getter,就是_vm的計算屬性。
接下來我們看下commit和dispatch是怎麼運作的,整個vuex也就差不多瞭解了。
commit的代碼如下:
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) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
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'
)
}
}
可以看到,commit邏輯其實就是遍歷_mutations中對應的handler,並調用。
再看下dispatch代碼:
dispatch(_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = {type, payload}
const entry = this._actions[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
this._actionSubscribers.forEach(sub => sub(action, this.state))
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
dispatch的邏輯也很清晰,就是把_actions中的處理函數執行一下,並返回。
到這裏,Vuex的整個流程就已經介紹完畢了。
總結
Vuex的整體流程還算比較清晰:
1. 對配置信息進行處理,state信息存放在_vm._data.$$state中,getters信息存放在_wrappedGetters中,並映射到Store的getters對象,getters對象的屬性再映射到_vm屬性中,mutations信息存放在_mutations中、actions的信息存放到_actions。
2. 當調用commit時,就是遍歷_mutations中的方法,並進行調用。
3. 當調用dispatch時,就是遍歷_actions中的方法,並進行調用。