乐趣区

vuex源码浅析

前言
当前版本是 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

退出移动版