前言
当前版本是3.1.0,这个版本主要引入了mutations的异步前后的两个钩子
debug的项目是官方例子里的shopping-cart,这个项目的有两个modules,可以看的比较直观。
个人理解
vuex就是维护一个Store对象的状态树。而他下一级如果直接就是state的话,那么当数据量大时就会很影响性能,通过分治的思想,引入了modules。
源码
constructor主要是进行一些初始化的操作
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
这里主要是判断是不是全局引入的vue及是不是浏览器环境,全局引入就自动注册进vue里,就是用npm引入的那种vue.use(vuex),install主要实现了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
}
}
}
这里显示判断vue的版本,如果是vue2就调用mixin,把vuexInit插入到beforeCreate钩子之前,vue1就不用说了….
回到constructor
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()
这里主要是初始化store内部的一些对象,关键是这个ModuleCollection,他把modules初始化之后,之后mutations改变state直接照这个_modules就好了
这里我们直接看代码可能会很难看懂,可以直接运行官方例子进行加深理解。
这里的options是
new Vuex.Store({
modules: {
cart,
products
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
我们进入ModuleCollection,
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
register (path, rawModule, runtime = true) {
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
这里的逻辑是给options里的每一个modules new一个modules对象,放在_children下,options放在_rawModule下,此时的this。_modules
ModuleCollection {root: Module}
root: Module
runtime: false
state: {}
_children:
cart: Module {runtime: false, _children: {…}, _rawModule: {…}, state: {…}}
products: Module {runtime: false, _children: {…}, _rawModule: {…}, state: {…}}
_rawModule:
modules: {cart: {…}, products: {…}}
plugins: [ƒ]
strict: true
__proto__: Object
namespaced: (...)
__proto__: Object
__proto__: Object
回到constructor
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
先看commit,在vuex里,改变state的唯一方法是提交commit来触发_mutations,actions最后也是通过_mutations来改变的
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'
)
}
}
先通过_type来找到_mutations,然后改变state之后,触发_subscribers,通知订阅者,实现数据的双向绑定。
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
}
try {
this._actionSubscribers
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
return res
})
}
这里由于_actions是异步的,所以会判断他是不是Promise,不是就new 一个Promise给他,这里需要的是_actionSubscribers运行了两次,这是这个版本加上的两个action的勾子函数。
回到constructor
installModule(this, state, [], this._modules.root)
这是注册_mutations, _actions等数据的
resetStoreVM(this, state)
这是注册订阅者的
// apply plugins
plugins.forEach(plugin => plugin(this))
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
这是装载插件,例如vue-devtools