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