本书将记录有关VUEX源码学习的心得体会,对应的VUEX的版本为2.4.1。全书分6个章节进行展开:vue
按照官方的说法:react
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
一般咱们在实例化一个store
的时候都是采用的下面这种方式:git
import Vuex from 'vuex' const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } })
有时候咱们将VUEX的state
混入到计算属性时会采用这种方式:github
// 在单独构建的版本中辅助函数为 Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭头函数可以使代码更简练 count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', // 为了可以使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } }) }
很明显,由上面代码能够看出,VUEX导出了一个对象,而Store、mapState
都只是这个对象的属性而已。那么,咱们不由会产生疑问:VUEX到底导出了些什么东西呢?vuex
咱们看看VUEX的源代码的 925 ~ 937 行:数组
var index = { Store: Store,//Store类 install: install,//install方法 version: '2.4.1', mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, mapActions: mapActions, createNamespacedHelpers: createNamespacedHelpers//基于命名空间的组件绑定辅助函数 }; return index;
VUEX采用的是典型的IIFE(当即执行函数表达式)模式,当代码被加载(经过<script>
或Vue.use()
)后,VUEX会返回一个对象,这个对象包含了Store
类、install
方法、mapState
辅助函数、mapMutations
辅助函数、mapGetters
辅助函数、mapActions
辅助函数、createNamespacedHelpers
辅助函数以及当前的版本号version
。app
纵观整个代码解构能够得出以下结论:异步
Module
类。ModuleCollection
类。Store
类。install
方法。mapState
辅助函数。mapMutations
辅助函数。mapGetters
辅助函数。mapActions
辅助函数。mapState
、mapMutations
、mapGetters
、mapActions
的辅助函数。从以上分析能够看出,VUEX源码主要由Store
类和mapState
、mapMutations
、mapGetters
、mapActions
四个辅助函数组成,其中Store
类又由ModuleCollection
实例组成,ModuleCollection
类又由Module
实例组成。VUEX源码就是经过这样的关系组织起来的。ide
Module类定义在VUEX源码的106 ~ 168 行,咱们来具体看看它的内容。
Module类的成员属性有四个,分别是:
其源码定义在106 ~ 112 行:
var Module = function Module (rawModule, runtime) { this.runtime = runtime; this._children = Object.create(null); this._rawModule = rawModule; var rawState = rawModule.state; this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}; };
该类的定义无特殊之处,但从中咱们能够看出,定义模块的state
时,并不必定须要它是一个对象,它也能够是返回一个对象的工厂函数。由于从代码的执行来看,当state
属性的值是一个函数时,会把这个函数的执行结果做为state
。这一点在官方文档中是没有说起的。
咱们能够经过下面这个例子来证实:
const store = new Vuex.Store({ state() { return { count: 0, todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] } } }) console.log(store)
其输出结果以下:
Module原型上分别定义了:
咱们来分别看一看这几个方法的实现。
addChild方法定义在VUEX源码的120 ~ 122 行,它的实现比较简单,就是将模块名称做为key,模块内容做为value定义在父模块的_children对象上:
Module.prototype.addChild = function addChild (key, module) { this._children[key] = module; };
removeChild方法定义在VUEX源码的124 ~ 126 行,它的实现也比较简单,就是采用删除对象属性的方法,将定义在父模块_children属性上的子模块delete:
Module.prototype.removeChild = function removeChild (key) { delete this._children[key]; };
getChild方法定义在VUEX源码的128 ~ 130 行,它的实现也比较简单,就是将父模块_children属性的子模块查找出来并return出去:
Module.prototype.getChild = function getChild (key) { return this._children[key] };
forEachChild方法定义在VUEX源码的145 ~ 147 行,它接受一个函数做为参数,而且将该函数应用在Module实例的_children属性上,也就是说会应用在全部的子模块上:
Module.prototype.forEachChild = function forEachChild (fn) { forEachValue(this._children, fn); };
能够看到forEachChild 方法其实是调用了另一个辅助函数forEachValue,这个函数接收Module实例的_children属性以及forEachChild 方法的fn参数做为参数。它其实是遍历_children对象,并将value和key做为fn的参执行fn。它定义在VUEX源码的87 ~ 92 行,咱们来看看它的实现:
/** * forEach for object */ function forEachValue (obj, fn) { Object.keys(obj).forEach(function (key) { return fn(obj[key], key); }); }
update方法定义在VUEX源码的132 ~ 143 行,它接收一个模块,而后用该模块更新当前Module实例上的_rawModule属性。更新的内容包括_rawModule的namespaced、actions、mutations、getters:
Module.prototype.update = function update (rawModule) { this._rawModule.namespaced = rawModule.namespaced; if (rawModule.actions) { this._rawModule.actions = rawModule.actions; } if (rawModule.mutations) { this._rawModule.mutations = rawModule.mutations; } if (rawModule.getters) { this._rawModule.getters = rawModule.getters; } };
forEachGetter方法定义在VUEX源码的149 ~ 153 行,同forEachChild方法原理相似,forEachGetter方法接受一个函数做为参数,而且将该函数应用在Module实例的_rawModule属性的getters上,也就是说会应用在该模块的全部getters上:
Module.prototype.forEachGetter = function forEachGetter (fn) { if (this._rawModule.getters) { forEachValue(this._rawModule.getters, fn); } };
能够看到forEachGetter 方法其实是也调用了另一个辅助函数forEachValue,这个forEachValue函数前面已经介绍过,这里就再也不赘述。
forEachAction方法定义在VUEX源码的155 ~ 159 行,同forEachChild、forEachGetter 方法原理相似,forEachAction方法接受一个函数做为参数,而且将该函数应用在Module实例的_rawModule属性的actions上,也就是说会应用在该模块的全部actions上:
Module.prototype.forEachAction = function forEachAction (fn) { if (this._rawModule.actions) { forEachValue(this._rawModule.actions, fn); } };
forEachMutation方法定义在VUEX源码的161 ~ 165 行,同forEachChild、forEachGetter、 forEachGetter 方法原理相似,forEachMutation方法接受一个函数做为参数,而且将该函数应用在Module实例的_rawModule属性的mutations上,也就是说会应用在该模块的全部mutations上:
Module.prototype.forEachMutation = function forEachMutation (fn) { if (this._rawModule.mutations) { forEachValue(this._rawModule.mutations, fn); } };
因为forEachChild、forEachGetter、 forEachGetter、forEachMutation方法相似,因此咱们这里仅以forEachMutation方法举一个例子,说明它及其实际执行者forEachValue的工做原理:
var options = { state() { return { count: 0, todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] } }, mutations: { increment (state) { state.count++ }, increment1 (state) { state.count++ } } } var moduleIns = new Module(options) moduleIns.forEachMutation(function (value, key) { console.log(`mutations key is : ${key}`) console.log(`mutations value is : ${value}`) })
这里咱们只是为了描述其原理,因此上述例子仅仅只是输出了motations的key和value,实际的使用场合会比这复杂的多,咱们来看看上述例子的输出结果:
mutations key is : increment mutations value is : increment(state) { state.count++ } mutations key is : increment1 mutations value is : increment1(state) { state.count++ }
ModuleCollection类定义在VUEX源码的169 ~ 251 行,咱们来具体看看它的内容。
Module类的成员属性只有1个:
它并非直接在构造函数中显示定义的,而是在原型函数register
中定义的。经过在构造函数中调用register
函数从而在成员属性root
上挂载根模块。其实如今VUEX源码的192 ~ 214 行:
ModuleCollection.prototype.register = function register (path, rawModule, runtime) { var this$1 = this; if ( runtime === void 0 ) runtime = true; { assertRawModule(path, rawModule); } var newModule = new Module(rawModule, runtime); if (path.length === 0) { this.root = newModule; } else { var parent = this.get(path.slice(0, -1)); parent.addChild(path[path.length - 1], newModule); } // register nested modules if (rawModule.modules) { forEachValue(rawModule.modules, function (rawChildModule, key) { this$1.register(path.concat(key), rawChildModule, runtime); }); } };
get方法定义在VUEX源码的174 ~ 178 行,get方法主要是根据给定的模块名(模块路径),从根store开始,逐级向下查找对应的模块找到最终的那个模块,它的核心是采用的reduce函数来实现的,咱们来看看它的源码:
ModuleCollection.prototype.get = function get (path) { return path.reduce(function (module, key) { return module.getChild(key) }, this.root) };
getNamespace方法定义在VUEX源码的180 ~ 186 行,getNamespace方法一样是根据给定的模块名(模块路径),从根store开始,逐级向下生成该模块的命名空间,当途中所遇到的模块没有设置namespaced属性的时候,其命名空间默认为空字符串,而若是设置了namespaced属性,则其命名空间是模块名+反斜线(/)拼接起来的字符。咱们来看看它的源码实现:
ModuleCollection.prototype.getNamespace = function getNamespace (path) { var module = this.root; return path.reduce(function (namespace, key) { module = module.getChild(key); return namespace + (module.namespaced ? key + '/' : '') }, '') };
update方法定义在VUEX源码的188 ~ 190 行,用于从根级别开始逐级更新模块的内容:
ModuleCollection.prototype.update = function update$1 (rawRootModule) { update([], this.root, rawRootModule); };
它实际调用的是定义在VUEX源码224 ~ 251 行的全局update方法:
function update (path, targetModule, newModule) { { assertRawModule(path, newModule); } // update target module targetModule.update(newModule); // update nested modules if (newModule.modules) { for (var key in newModule.modules) { if (!targetModule.getChild(key)) { { console.warn( "[vuex] trying to add a new module '" + key + "' on hot reloading, " + 'manual reload is needed' ); } return } update( path.concat(key), targetModule.getChild(key), newModule.modules[key] ); } } }
这个方法会调用指定模块(第二个参数)的update方法,咱们在第二章介绍过,每一个Module实例都有一个update原型方法,定义在132 ~ 143行,这里再一次粘贴以下:
Module.prototype.update = function update (rawModule) { this._rawModule.namespaced = rawModule.namespaced; if (rawModule.actions) { this._rawModule.actions = rawModule.actions; } if (rawModule.mutations) { this._rawModule.mutations = rawModule.mutations; } if (rawModule.getters) { this._rawModule.getters = rawModule.getters; } };
回到全局update方法,当判断出它还有子模块的时候,则会递归地调用update方法进行模块更新对应子模块。
register方法定义在VUEX源码的192 ~ 214 行,它主要是从根级别开始,逐级注册子模块,最终的模块链条会挂载在ModuleCollection实例的成员属性root上,咱们来看看它的源码:
ModuleCollection.prototype.register = function register (path, rawModule, runtime) { var this$1 = this; if ( runtime === void 0 ) runtime = true; { assertRawModule(path, rawModule); } var newModule = new Module(rawModule, runtime); if (path.length === 0) { this.root = newModule; } else { var parent = this.get(path.slice(0, -1)); parent.addChild(path[path.length - 1], newModule); } // register nested modules if (rawModule.modules) { forEachValue(rawModule.modules, function (rawChildModule, key) { this$1.register(path.concat(key), rawChildModule, runtime); }); } };
咱们来过一遍该代码,这段代码首先保存了this副本:
var this$1 = this;
而后会判断runtime参数是否传递,若是没有传递,则会给它默认设置为ture:
if ( runtime === void 0 ) runtime = true; { assertRawModule(path, rawModule); }
这里有一个小知识点是采用void 0 判断undfined,这是一种很好的方法,具体缘由能够参考本人的这篇文章“JavaScrip中如何正确并优雅地判断undefined”。接下来会实例化一个Module,对于根Module而言,它会被挂载到ModuleCollection实例的root成员属性上,而对于子模块,它会找到它的父模块,而后挂载到父模块的_children
属性上,由Module类咱们知道,每个模块都会有一个_children
属性,用于存储它的子模块:
var newModule = new Module(rawModule, runtime); if (path.length === 0) { this.root = newModule; } else { var parent = this.get(path.slice(0, -1)); parent.addChild(path[path.length - 1], newModule); }
最后面的代码就是递归的调用register函数用来执行前面几个步骤:
// register nested modules if (rawModule.modules) { forEachValue(rawModule.modules, function (rawChildModule, key) { this$1.register(path.concat(key), rawChildModule, runtime); }); }
那么register函数是在哪里调用的呢?它是在VUEX源码169 ~ 172行ModuleCollection的构造函数中调用的,咱们来看看:
var ModuleCollection = function ModuleCollection (rawRootModule) { // register root module (Vuex.Store options) this.register([], rawRootModule, false); };
咱们能够经过一个例子来直观地看一看:
const moduleC = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } } const moduleA = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }, modules: { c: moduleC } } const moduleB = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } } var options = { state() { return { count: 0, todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] } }, mutations: { increment (state) { state.count++ }, increment1 (state) { state.count++ } }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }, actions: { increment (context) { context.commit('increment') } }, modules: { a: moduleA, b: moduleB } } var moduleCollectionIns = new ModuleCollection(options) console.log(moduleCollectionIns)
输出结果:
unrigister方法定义在VUEX源码的216 ~ 222 行,它用于取消注册某个模块:
ModuleCollection.prototype.unregister = function unregister (path) { var parent = this.get(path.slice(0, -1)); var key = path[path.length - 1]; if (!parent.getChild(key).runtime) { return } parent.removeChild(key); };
当取消注册某个模块时须要先拿到该模块的父模块,而后在父模块的_children对象中删除该模块,调用的是父模块的removeChild方法。这里面会判断待取消模块是否处于运行时(runtime),当不处于运行时(runtime)时能够取消注册。
Store类定义在VUEX源码的294 ~ 775 行,是VUEX中最后定义的、最重要的类,也是咱们实际上最后使用的类。咱们来具体看看它的内容。
Store类的成员属性主要有下面几个:
其源码定义在296 ~ 362 Store类的构造函数中,咱们来看看:
var Store = function Store (options) { var this$1 = this; if ( options === void 0 ) 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); } { 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."); } var plugins = options.plugins; if ( plugins === void 0 ) plugins = []; var strict = options.strict; if ( strict === void 0 ) strict = false; var state = options.state; if ( state === void 0 ) state = {}; if (typeof state === 'function') { state = state() || {}; } // 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 var store = this; var ref = this; var dispatch = ref.dispatch; var commit = ref.commit; 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; // 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(function (plugin) { return plugin(this$1); }); if (Vue.config.devtools) { devtoolPlugin(this); } };
该类会首先检查咱们在声明Store实例的时候有没有传递options参数,若是没有则会初始化为空对象{}。而后会检查Vue有没有定义,若是Vue没有定义,而且window上已经有挂载Vue,那么会安装Vue,不然告警:
// 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); } { 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."); }
这段代码能够确保Vue被且仅被一次安装。Vue是全局声明在VUEX源码中的294行:
var Vue; // bind on install
而若是咱们在页面使用了Vue(不论是脚本引入<script src="./vue.js"></script>仍是node引入模式),在window上都会挂载一个Vue:
而安装Vue的install方法定义在VUEX源码的777 ~ 788行,主要就是给全局声明的Vue赋值,而后调用了applyMixin执行混入:
function install (_Vue) { if (Vue && _Vue === Vue) { { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ); } return } Vue = _Vue; applyMixin(Vue); }
applyMixin定义在VUEX源码的12 ~ 29行,它的主要目的是确保在Vue的beforeCreate钩子函数中调用vuexInit函数,固然更具Vue版本差别实现方法也有差别,由于咱们主要针对>2的版本,因此这里只看版本>2时的状况:
var applyMixin = function (Vue) { var 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. var _init = Vue.prototype._init; Vue.prototype._init = function (options) { if ( options === void 0 ) 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 () { var 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; } } };
在该函数内部定义了vuexInit函数,该函数的主要做用是在Vue的实例上挂载$store:
回过头来继续看Store构造函数,在执行完install以后,它会对构造函数的options参数进行检查,当不合法时会给出默认值:
var plugins = options.plugins; if ( plugins === void 0 ) plugins = []; var strict = options.strict; if ( strict === void 0 ) strict = false; var state = options.state; if ( state === void 0 ) state = {}; if (typeof state === 'function') { state = state() || {}; }
这里面有两点须要注意:
接下来就是成员属性的定义:
// 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();
再接下来就是成员函数dispatch和commit的定义,这个咱们在下一节详细讲述。
接下来会执行installModule函数:
// init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root);
英文的注释已经描述的很详细了,installModule会初始化根模块,并递归地注册子模块,收集全部模块的Getters放在_wrappedGetters属性中。installModule是一个全局函数,定义在VUEX源码的577 ~ 617 行:
function installModule (store, rootState, path, module, hot) { var isRoot = !path.length; var namespace = store._modules.getNamespace(path); // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module; } // set state if (!isRoot && !hot) { var parentState = getNestedState(rootState, path.slice(0, -1)); var moduleName = path[path.length - 1]; store._withCommit(function () { Vue.set(parentState, moduleName, module.state); }); } var local = module.context = makeLocalContext(store, namespace, path); module.forEachMutation(function (mutation, key) { var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }); module.forEachAction(function (action, key) { var type = action.root ? key : namespace + key; var handler = action.handler || action; registerAction(store, type, handler, local); }); module.forEachGetter(function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); }); module.forEachChild(function (child, key) { installModule(store, rootState, path.concat(key), child, hot); }); }
代码开始会判断是不是根模块,而且会获取模块对应的命名空间,若是该模块有namespace属性,则在_modulesNamespaceMap属性上以命名空间为key保存该模块:
var isRoot = !path.length; var namespace = store._modules.getNamespace(path); // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module; }
下面这个貌似意思是将该模块的state以命名空间为key挂载父模块的state上,造成state链,这个过程是为了后面使用getNestedState函数查找对应命名空间的state:
// set state if (!isRoot && !hot) { var parentState = getNestedState(rootState, path.slice(0, -1)); var moduleName = path[path.length - 1]; store._withCommit(function () { Vue.set(parentState, moduleName, module.state); }); }
咱们来看一个根store上挂载namespaced的a模块,a模块又挂载namespaced的c模块的例子,此时根store的state长这样:
回过头来看installModule,接下来是拿到本地的上下文:
var local = module.context = makeLocalContext(store, namespace, path);
本地上下文是什么意思呢?意思就是说,dispatch, commit, state, getters都是局部化了。咱们知道store的模块是有命名空间的概念的,要想操做某一层级的东西,都是须要用命名空间去指定的。若是不使用命名空间,操做的是根级别的。因此本地上下文是指的就是某个模块的上下文,当你操做dispatch, commit, state, getters等的时候,你实际上直接操做的某个模块。
这个看似复杂的东西是如何实现的呢?其实它只不过是个语法糖,它内部也是经过逐级查找,找到对应的模块完成的。咱们来看看这个makeLocalContext的实现,它定义在VUEX源码的618 ~ 675 行:
/** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */ function makeLocalContext (store, namespace, path) { var noNamespace = namespace === ''; var local = { dispatch: noNamespace ? store.dispatch : function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (!options || !options.root) { type = namespace + type; if ("development" !== 'production' && !store._actions[type]) { console.error(("[vuex] unknown local action type: " + (args.type) + ", global type: " + type)); return } } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (!options || !options.root) { type = namespace + type; if ("development" !== 'production' && !store._mutations[type]) { console.error(("[vuex] unknown local mutation type: " + (args.type) + ", global type: " + type)); return } } store.commit(type, payload, options); } }; // getters and state object must be gotten lazily // because they will be changed by vm update Object.defineProperties(local, { getters: { get: noNamespace ? function () { return store.getters; } : function () { return makeLocalGetters(store, namespace); } }, state: { get: function () { return getNestedState(store.state, path); } } }); return local }
该函接受根级别的store、命名空间、模块路径做为参数,而后声明一个local对象,分别局部化dispatch, commit, getters , state。咱们来分别看一下:
局部化dispatch:
dispatch局部化时,首先判断有没有命名空间,若是没有则直接使用根级别的dispatch方法,若是有,则从新定义该方法:
function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (!options || !options.root) { type = namespace + type; if ("development" !== 'production' && !store._actions[type]) { console.error(("[vuex] unknown local action type: " + (args.type) + ", global type: " + type)); return } } return store.dispatch(type, payload) }
从新定义其实只不过是规范化参数,将参数映射到指定命名空间的模块上,是调用unifyObjectStyle来完成的,它定义在VUEX源码的763 ~ 775 行,咱们来看看它的实现:
function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload; payload = type; type = type.type; } { assert(typeof type === 'string', ("Expects string as the type, but found " + (typeof type) + ".")); } return { type: type, payload: payload, options: options } }
规范化参数实际上是和官方文档关于dispatch的描述是呼应的,咱们引用一下官方的表述:
Actions 支持一样的载荷方式和对象方式进行分发:
// 以载荷形式分发 store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })
能够看出unifyObjectStyle只不过是把载荷形式的分发转换成了对象形式的分发,这种状况下的options实际上是undefined。
回过头来继续看dispatch的局部化过程。当规范化参数会分别拿到规范化的参数,而后对于非根级别,则给type加上命名空间:
var payload = args.payload; var options = args.options; var type = args.type; if (!options || !options.root) { type = namespace + type; if ("development" !== 'production' && !store._actions[type]) { console.error(("[vuex] unknown local action type: " + (args.type) + ", global type: " + type)); return } }
最后仍是调用根级别的dispatch来完成分发:
return store.dispatch(type, payload)
局部化commit:
commit的局部化以下:
commit: noNamespace ? store.commit : function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (!options || !options.root) { type = namespace + type; if ("development" !== 'production' && !store._mutations[type]) { console.error(("[vuex] unknown local mutation type: " + (args.type) + ", global type: " + type)); return } } store.commit(type, payload, options); } };
整个过程和dispatch的局部化过程几乎如出一辙,这里咱们就再也不赘述。
局部化Getters:
Getters的局部化时在前述的local对象上定义getters属性,并从新定义该属性的get函数:
getters: { get: noNamespace ? function () { return store.getters; } : function () { return makeLocalGetters(store, namespace); } },
其主要思路就是:没有命名空间的时候直接拿根级别的getters,有命名空间的时候拿对应模块上的getters。这其中用到了makeLocalGetters函数,它定义在VUEX源码的677 ~ 698 行,咱们来看看它的实现:
function makeLocalGetters (store, namespace) { var gettersProxy = {}; var splitPos = namespace.length; Object.keys(store.getters).forEach(function (type) { // skip if the target getter is not match this namespace if (type.slice(0, splitPos) !== namespace) { return } // extract local getter type var localType = type.slice(splitPos); // Add a port to the getters proxy. // Define as getter property because // we do not want to evaluate the getters in this time. Object.defineProperty(gettersProxy, localType, { get: function () { return store.getters[type]; }, enumerable: true }); }); return gettersProxy }
咱们只须要遍历根级别store的getters属性,找到对应的命名空间,而后代理对它的访问就能够了。咱们能够先来看一个例子以及根级别上getters的内容,相信对理解上述代码会更用帮助:
const moduleC = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } } const moduleA = { namespaced: true, state: { count: 1, age1: 20 }, getters: { doubleCount (state) { return state.count * 2 } }, modules: { c: moduleC } } const moduleB = { namespaced: true, state: { count: 1, age1: 20 }, getters: { doubleCount (state) { return state.count * 2 } } } const store = new Vuex.Store({ state() { return { count: 0, todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] } }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }, modules: { a: moduleA, b: moduleB } }) var vm = new Vue({ el: '#example', data: { age: 10 }, store, mounted() { console.log(this.count) this.localeincrement('hehe') console.log(this.count) }, // computed: Vuex.mapState('a', [ // 'count', 'age1' // ] // ), computed: Vuex.mapState([ 'count' ] ), methods: { ...Vuex.mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` }), ...Vuex.mapMutations({ localeincrement (commit, args) { console.log(commit) console.log(args) commit('increment', args) } }) } }) console.log(vm)
对应的根级别的getters:
局部化state:
state的局部化时在前述的local对象上定义state属性,并从新定义该属性的get函数:
state: { get: function () { return getNestedState(store.state, path); } }
它会调用getNestedState方法由根实例的state向下查找对应命名空间的state, getNestedState定义在VUEX源码的757 ~ 761 行:
function getNestedState (state, path) { return path.length ? path.reduce(function (state, key) { return state[key]; }, state) : state }
以上面的例子为例,咱们来看一下a模块和c模块的state关系就知道了:
全部的局部化完成以后会将该local对象返回,挂载在根模块的context属性上:
return local
咱们来看看例子:
回过头来继续看installModule函数的执行,它会分别遍历mutaions, actions, getters,并分别执行registerMutation,registerAction,registerGetter:
module.forEachMutation(function (mutation, key) { var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }); module.forEachAction(function (action, key) { var type = action.root ? key : namespace + key; var handler = action.handler || action; registerAction(store, type, handler, local); }); module.forEachGetter(function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); });
咱们来分别看一下:
注册Mutation:
注册mutaion首先会调用forEachMutation进行遍历:
module.forEachMutation(function (mutation, key) { var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); });
forEachMutation的实如今VUEX源码的161 ~ 165行:
Module.prototype.forEachMutation = function forEachMutation (fn) { if (this._rawModule.mutations) { forEachValue(this._rawModule.mutations, fn); } };
forEachValue的实现咱们在前面讲过,实际上就是遍历对象的key,将value,key做为参数应用于fn。而对于forEachAction而言,它的fn就是:
function (action, key) { var type = action.root ? key : namespace + key; var handler = action.handler || action; registerAction(store, type, handler, local); }
这里的核心仍是归结到使用命名空间注册action了,它实际调用的是registerAction,该函数定义在VUEX源码的700 ~ 705 行:
function registerMutation (store, type, handler, local) { var entry = store._mutations[type] || (store._mutations[type] = []); entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload); }); }
实际上,它是在根store的_mutaions上以命名空间为key,注册对应的mutaion。稍后咱们会有实际例子展现。
注册Action:
同注册Mutation原理如出一辙,注册action首先会调用forEachAction进行遍历:
module.forEachAction(function (action, key) { var type = action.root ? key : namespace + key; var handler = action.handler || action; registerAction(store, type, handler, local); });
forEachAction的实如今VUEX源码的155 ~ 159行:
Module.prototype.forEachAction = function forEachAction (fn) { if (this._rawModule.actions) { forEachValue(this._rawModule.actions, fn); } };
forEachValue的实现咱们在前面讲过,实际上就是遍历对象的key,将value,key做为参数应用于fn。而对于forEachMutation而言,它的fn就是:
function (mutation, key) { var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }
这里的核心仍是归结到使用命名空间注册mutation了,它实际调用的是registerMutation,该函数定义在VUEX源码的707 ~ 730 行:
function registerAction (store, type, handler, local) { var entry = store._actions[type] || (store._actions[type] = []); entry.push(function wrappedActionHandler (payload, cb) { var 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(function (err) { store._devtoolHook.emit('vuex:error', err); throw err }) } else { return res } }); }
实际上,它是在根store的_actions上以命名空间为key,注册对应的action。可是action比mutaion复杂,主要体如今:
对于第二点,从代码中,咱们能够看出,action实际上的会暴露四个参数:
action和mutations的最大区别还在于,action是支持异步的。这在上述代码也有体现。
稍后咱们会有实际例子展现注册action的效果。
注册Getters:
同注册Mutation、Action原理相似,注册getters首先会调用forEachGetter进行遍历:
module.forEachGetter(function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); });
forEachGetter的实如今VUEX源码的149 ~ 153行:
Module.prototype.forEachGetter = function forEachGetter (fn) { if (this._rawModule.getters) { forEachValue(this._rawModule.getters, fn); } };
forEachValue的实现咱们在前面讲过,实际上就是遍历对象的key,将value,key做为参数应用于fn。而对于forEachGetter而言,它的fn就是:
function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); }
这里的核心仍是归结到使用命名空间注册getters了,它实际调用的是registerGetter,该函数定义在VUEX源码的732 ~ 747 行:
function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { { console.error(("[vuex] duplicate getter key: " + type)); } return } store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }; }
实际上,它是在根store的_wrappedGetters上以命名空间为key,注册对应的getters。它其实是对getters对应的handle参数作了处理,暴露出四个参数:局部化的state、局部化的getters、根级别的state、根级别的getters。
以上Mutation、Action、Getters的注册能够经过下面这个例子来加深理解:
const moduleC = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } } const moduleA = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, actions: { increment (context) { context.commit('increment') } }, getters: { doubleCount (state) { return state.count * 2 } }, modules: { c: moduleC } } const moduleB = { namespaced: true, state: { count: 1, age1: 20 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, actions: { increment (context) { context.commit('increment') } }, getters: { doubleCount (state) { return state.count * 2 } } } const store = new Vuex.Store({ state() { return { count: 0, todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] } }, mutations: { increment (state) { state.count++ }, increment1 (state) { state.count++ } }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }, actions: { increment (context) { context.commit('increment') } }, modules: { a: moduleA, b: moduleB } }) var vm = new Vue({ el: '#example', data: { age: 10 }, store, mounted() { console.log(this.count) this.localeincrement('hehe') console.log(this.count) }, // computed: Vuex.mapState('a', [ // 'count', 'age1' // ] // ), computed: Vuex.mapState([ 'count' ] ), methods: { ...Vuex.mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` }), ...Vuex.mapMutations({ localeincrement (commit, args) { console.log(commit) console.log(args) commit('increment', args) } }) } }) console.log(vm)
分别看看注册的结果:
回过头来继续看installModule的执行,它会遍历该模块的子模块,递归调用installModule来完成上述注册:
module.forEachChild(function (child, key) { installModule(store, rootState, path.concat(key), child, hot); });
而forEachChild定义在VUEX源码的145 ~ 147 行:
Module.prototype.forEachChild = function forEachChild (fn) { forEachValue(this._children, fn); };
至此,installModule的执行就jies 了,咱们回过头继续看看Store类的构造函数执行,接下来执行的是resetStoreVM函数,它定义在VUEX源码的531 ~ 575 行:
function resetStoreVM (store, state, hot) { var oldVm = store._vm; // bind store public getters store.getters = {}; var wrappedGetters = store._wrappedGetters; var computed = {}; forEachValue(wrappedGetters, function (fn, key) { // use computed to leverage its lazy-caching mechanism computed[key] = function () { return fn(store); }; Object.defineProperty(store.getters, key, { get: function () { return 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 var silent = Vue.config.silent; Vue.config.silent = true; store._vm = new Vue({ data: { $$state: state }, computed: 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(function () { oldVm._data.$$state = null; }); } Vue.nextTick(function () { return oldVm.$destroy(); }); } }
它实际上主要作的是在store的实例上定义vm属性,而vm上挂载的是一个新的Vue实例,这个vue实例的data为store的state,而computed为store的getters,咱们一样之前面的那个例子,看看效果:
Store构造函数的最后是和插件、调试工具备关的几行代码,这里再也不细述:
// apply plugins plugins.forEach(function (plugin) { return plugin(this$1); }); if (Vue.config.devtools) { devtoolPlugin(this); }
commit定义在VUEX源码的376 ~ 409行:
Store.prototype.commit = function commit (_type, _payload, _options) { var this$1 = this; // check object-style commit var ref = unifyObjectStyle(_type, _payload, _options); var type = ref.type; var payload = ref.payload; var options = ref.options; var mutation = { type: type, payload: payload }; var entry = this._mutations[type]; if (!entry) { { console.error(("[vuex] unknown mutation type: " + type)); } return } this._withCommit(function () { entry.forEach(function commitIterator (handler) { handler(payload); }); }); this._subscribers.forEach(function (sub) { return sub(mutation, this$1.state); }); if ( "development" !== 'production' && options && options.silent ) { console.warn( "[vuex] mutation type: " + type + ". Silent option has been removed. " + 'Use the filter functionality in the vue-devtools' ); } };
它所作的就是当mutation被提交时执行对应的函数,而且还会执行订阅列表里面的回调函数。
dispatch定义在VUEX源码的411 ~ 433 行,它的原理和commit基本上同样的,也是在分发action时执行对应的函数,而且执行订阅action的列表,所不一样的是action是支持异步的:
Store.prototype.dispatch = function dispatch (_type, _payload) { var this$1 = this; // check object-style dispatch var ref = unifyObjectStyle(_type, _payload); var type = ref.type; var payload = ref.payload; var action = { type: type, payload: payload }; var entry = this._actions[type]; if (!entry) { { console.error(("[vuex] unknown action type: " + type)); } return } this._actionSubscribers.forEach(function (sub) { return sub(action, this$1.state); }); return entry.length > 1 ? Promise.all(entry.map(function (handler) { return handler(payload); })) : entry[0](payload) };
subscribe定义在VUEX源码的435 ~ 437行,用于注册订阅mutation的回调:
Store.prototype.subscribe = function subscribe (fn) { return genericSubscribe(fn, this._subscribers) };
官方描述以下:
注册监听 store 的 mutation。handler 会在每一个 mutation 完成后调用,接收 mutation 和通过 mutation 后的状态做为参数:
store.subscribe((mutation, state) => { console.log(mutation.type) console.log(mutation.payload) })一般用于插件。
它实际上调用的是定义在VUEX源码507 ~ 517行的genericSubscribe函数:
function genericSubscribe (fn, subs) { if (subs.indexOf(fn) < 0) { subs.push(fn); } return function () { var i = subs.indexOf(fn); if (i > -1) { subs.splice(i, 1); } } }
它实际上就是订阅mutation,并将回调放入_subscribers订阅列表中,它会返回一个函数,用于解除订阅。这个主要用在调试工具里。
subscribeAction定义在VUEX源码的439 ~ 441行,用于注册订阅action的回调,它和subscribe函数的原理是如出一辙的:
Store.prototype.subscribeAction = function subscribeAction (fn) { return genericSubscribe(fn, this._actionSubscribers) };
它实际上也调用的是定义在VUEX源码507 ~ 517行的genericSubscribe函数,这个在前面已经讲过了。它实际上就是订阅action,并将回调放入_actionSubscribers订阅列表中,它会返回一个函数,用于解除订阅。这个也主要用在调试工具里。
watch定义在VUEX源码的433 ~ 450行:
Store.prototype.watch = function watch (getter, cb, options) { var this$1 = this; { assert(typeof getter === 'function', "store.watch only accepts a function."); } return this._watcherVM.$watch(function () { return getter(this$1.state, this$1.getters); }, cb, options) };
咱们来直接看看官方文档对它的解释吧:
响应式地监测一个 getter 方法的返回值,当值改变时调用回调函数。getter 接收 store 的状态做为惟一参数。接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数。
要中止监测,直接调用返回的处理函数。
replcaeState定义在VUEX源码的452 ~ 489行,用于替换_vm属性上存储的状态:
Store.prototype.replaceState = function replaceState (state) { var this$1 = this; this._withCommit(function () { this$1._vm._data.$$state = state; }); };
registerModule定义在VUEX源码的第460 ~ 474 行,使得Store实例可以在给定路径注册相应的模块,实际上仍是从根模块开始,找到对应的路径,而后注册。注册完成后须要从新安装模块,而后重置_vm属性:
Store.prototype.registerModule = function registerModule (path, rawModule, options) { if ( options === void 0 ) options = {}; if (typeof path === 'string') { path = [path]; } { assert(Array.isArray(path), "module path must be a string or an Array."); assert(path.length > 0, 'cannot register the root module by using registerModule.'); } this._modules.register(path, rawModule); installModule(this, this.state, path, this._modules.get(path), options.preserveState); // reset store to update getters... resetStoreVM(this, this.state); };
unregisterModule定义在VUEX源码的第476 ~ 491行,它使得Store实例能够经过提供的path参数解除对应模块的注册。实际上它仍是根据path找到对应的模块的父模块,而后调用父模块的unregister方法完成解绑:
Store.prototype.unregisterModule = function unregisterModule (path) { var this$1 = this; if (typeof path === 'string') { path = [path]; } { assert(Array.isArray(path), "module path must be a string or an Array."); } this._modules.unregister(path); this._withCommit(function () { var parentState = getNestedState(this$1.state, path.slice(0, -1)); Vue.delete(parentState, path[path.length - 1]); }); resetStore(this); };
hotUpdate定义在VUEX源码的493 ~ 496行:
Store.prototype.hotUpdate = function hotUpdate (newOptions) { this._modules.update(newOptions); resetStore(this, true); };
hotUpdate能够热更新整个模块,跟新完后调用resetStore重置整个模块,resetStore的定义在519 ~ 529行:
function resetStore (store, hot) { store._actions = Object.create(null); store._mutations = Object.create(null); store._wrappedGetters = Object.create(null); store._modulesNamespaceMap = Object.create(null); var state = store.state; // init all modules installModule(store, state, [], store._modules.root, true); // reset vm resetStoreVM(store, state, hot); }
能够看到基本上就是将构造函数推倒重来了一遍。
_withCommit定义在VUEX源码的498 ~ 503行:
Store.prototype._withCommit = function _withCommit (fn) { var committing = this._committing; this._committing = true; fn(); this._committing = committing; };
它首先设置当前store的committing状态为true,表示正在commit,而后执行对应的函数,当执行完毕后,重置commit状态。