之前看了一下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,这里的
s
t
o
r
e
,
这
里
的
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. store.state就是用store中的vm.data. s t o r e . s t a t e 就 是 用 s t o r e 中 的 v m . d a t a . state,而这个 s t a t e , 而 这 个 $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中的方法,并进行调用。