vuex做为配合vue使用的数据状态管理库,针对解决兄弟组件或多层级组件共享数据状态的痛点问题来讲,很是好用。本文以使用者的角度,结合源码来学习vuex。其中也参考了许多前辈的文章,参见最后的Referencehtml
Vue加载Vuex仍是很简单的,让咱们以官方文档上实例为切入点来开始认识Vuexvue
import Vue from 'vue' import Vuex from 'vuex' import cart from './modules/cart' import products from './modules/products' import createLogger from '../../../src/plugins/logger' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : [] })
这段代码咱们再熟悉不过了,就是Vue加载Vuex插件,而后new了一个Vuex实例。
咱们一步一步来看,首先看一下Vue如何加载的Vuex,也就是Vue.use(Vuex)
发生了什么。react
Vue.use = function (plugin: Function | Object) { /* istanbul ignore if */ /*标识位检测该插件是否已经被安装*/ if (plugin.installed) { return } // additional parameters const args = toArray(arguments, 1) /*将this(Vue构造函数)加入数组头部*/ args.unshift(this) if (typeof plugin.install === 'function') { /*install执行插件安装*/ plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } //标记插件已安装 plugin.installed = true return this }
主要作了几件事:git
那么Vuex提供install方法了吗?答案是确定的github
let Vue // bind on install export function install (_Vue) { if (Vue) { /*避免重复安装(Vue.use内部也会检测一次是否重复安装同一个插件)*/ if (process.env.NODE_ENV !== 'production') { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } /*保存Vue,同时用于检测是否重复安装*/ Vue = _Vue//Vue构造函数 /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/ applyMixin(Vue) }
看mixin以前咱们能够先思考一个问题,咱们在访问Vuex的数据的时候基本都是这样访问的,好比this.user = this.$store.state.global.user
,this.$store
是何时加到Vue实例上的?applyMixin会给出答案,让咱们继续看applyMixin
发生了什么web
// applyMixin: export default function (Vue) { /*获取Vue版本,鉴别Vue1.0仍是Vue2.0*/ const version = Number(Vue.version.split('.')[0]) if (version >= 2) { /*经过mixin将vuexInit混淆到Vue实例的beforeCreate钩子中*/ Vue.mixin({ beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. /*将vuexInit放入_init中调用*/ 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. */ /*Vuex的init钩子,会存入每个Vue实例等钩子列表*/ function vuexInit () { // this = vue object const options = this.$options // store injection if (options.store) { /*存在store其实表明的就是Root节点,直接执行store(function时)或者使用store(非function)*/ this.$store = typeof options.store === 'function' ? options.store() : options.store } else if (options.parent && options.parent.$store) { /*子组件直接从父组件中获取$store,这样就保证了全部组件都公用了全局的同一份store*/ this.$store = options.parent.$store } } }
咱们这里就只看2.0了,思路就是经过Vue.mixin
把挂载$store的动做放在beforeCreate
钩子上,由此实现了每一个组件实例均可以经过this.$store
来直接访问数据。
注意:mixin的细节vuex
至此,Vue.use(Vuex)咱们已经了解完了。api
顺着咱们实例代码的思路,接下来咱们应该开始看构造器了,不过开始看以前,咱们先看一下Vuex.store Class都定义了些什么。数组
export class Store { constructor (options = {}) { } // state 取值函数(getter) get state () { } //存值函数(setter) set state (v) { } /* 调用mutation的commit方法 */ commit (_type, _payload, _options) { } /* 调用action的dispatch方法 */ dispatch (_type, _payload) { } /* 注册一个订阅函数,返回取消订阅的函数 */ subscribe (fn) { } /* 观察一个getter方法 */ watch (getter, cb, options) { } /* 重置state */ replaceState (state) { } /* 注册一个动态module,当业务进行异步加载的时候,能够经过该接口进行注册动态module */ registerModule (path, rawModule) { } /* 注销一个动态module */ unregisterModule (path) { } /* 热更新 */ hotUpdate (newOptions) { } /* 保证经过mutation修改store的数据 */ // 内部使用,好比当外部强行改变state的数据时直接报错 _withCommit (fn) { } }
以上就是定义的接口了,官方文档上实例属性和方法都在这里找获得。
来看一张大神画的图有助于理解思路浏览器
出处见最后。
接下来咱们继续实例思路
export default new Vuex.Store({ modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : [] })
来看一下构造函数
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 /* 在浏览器环境下,若是插件还未安装(!Vue即判断是否未安装),则它会自动安装。 它容许用户在某些状况下避免自动安装。 */ if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) // 将store注册到实例或conponent } 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.`) //检查是否是new 操做符调用的 assert(this instanceof Store, `Store must be called with the new operator.`) } const { /*一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 做为惟一参数,能够监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)*/ plugins = [], /*使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数之外修改 Vuex state 都会抛出错误。*/ strict = false } = options /*从option中取出state,若是state是function则执行,最终获得一个对象*/ let { state = {} } = options if (typeof state === 'function') { state = state() } // store internal state /* 用来判断严格模式下是不是用mutation修改state的 */ this._committing = false /* 存放action */ this._actions = Object.create(null) /* 存放mutation */ this._mutations = Object.create(null) /* 存放getter */ //包装后的getter this._wrappedGetters = Object.create(null) /* module收集器 */ this._modules = new ModuleCollection(options) /* 根据namespace存放module */ this._modulesNamespaceMap = Object.create(null) /* 存放订阅者 外部插件使用 */ this._subscribers = [] /* 用以实现Watch的Vue实例 */ this._watcherVM = new Vue() // bind commit and dispatch to self /*将dispatch与commit调用的this绑定为store对象自己,不然在组件内部this.dispatch时的this会指向组件的vm*/ const store = this const {dispatch, commit} = this /* 为dispatch与commit绑定this(Store实例自己) */ 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 /*严格模式(使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数之外修改 Vuex state 都会抛出错误)*/ this.strict = strict // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters /*初始化根module,这也同时递归注册了全部子modle,收集全部module的getter到_wrappedGetters中去,this._modules.root表明根module才独有保存的Module对象*/ installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) /* 经过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */ resetStoreVM(this, state) // apply plugins /* 调用插件 */ plugins.forEach(plugin => plugin(this)) /* devtool插件 */ if (Vue.config.devtools) { devtoolPlugin(this) } }
Vuex的源码一共就一千行左右,构造函数吃透基本掌握至少一半了,构造函数中主要是初始化各类属性。简单的详见注释,这里咱们主要看如何解析处理modules
,首先来看this._modules = new ModuleCollection(options)
,ModuleCollection结构以下
import Module from './module' import { assert, forEachValue } from '../util' /*module收集类*/ export default class ModuleCollection { constructor (rawRootModule) { // new store(options) // register root module (Vuex.Store options) this.register([], rawRootModule, false) } /*获取父级module*/ get (path) { } /* 获取namespace,当namespaced为true的时候会返回'moduleName/name' 默认状况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块可以对同一 mutation 或 action 做出响应。 若是但愿你的模块更加自包含或提升可重用性,你能够经过添加 namespaced: true 的方式使其成为命名空间模块。 当模块被注册后,它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。 */ getNamespace (path) { } update (rawRootModule) { } /*注册*/ register (path, rawModule, runtime = true) { if (process.env.NODE_ENV !== 'production') { assertRawModule(path, rawModule) } /*新建一个Module对象*/ const newModule = new Module(rawModule, runtime) if (path.length === 0) { /*path为空数组的表明跟节点*/ this.root = newModule } else { /*获取父级module*/ const parent = this.get(path.slice(0, -1))//排除倒数第一个元素的数组, /*在父module中插入一个子module*/ parent.addChild(path[path.length - 1], newModule) } // register nested modules /*递归注册module*/ if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { // concat不改变源数组,返回合并后的数组 this.register(path.concat(key), rawChildModule, runtime) }) } } /*注销*/ unregister (path) { } } /*Module构造类*/ export default class Module { constructor (rawModule, runtime) { this.runtime = runtime this._children = Object.create(null) /*保存module*/ this._rawModule = rawModule /*保存modele的state*/ const rawState = rawModule.state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} } /* 获取namespace */ get namespaced () { } /*插入一个子module,存入_children中*/ addChild (key, module) { this._children[key] = module } /*移除一个子module*/ removeChild (key) { } /*根据key获取子module*/ getChild (key) { return this._children[key] } /* 更新module */ update (rawModule) { } /* 遍历child */ forEachChild (fn) { } /* 遍历getter */ forEachGetter (fn) { } /* 遍历action */ forEachAction (fn) { } /* 遍历matation */ forEachMutation (fn) { } }
ModuleCollection
的做用就是收集和管理Module
,构造函数中对咱们传入的options的module进行了注册(register 是重点,详见注释,注册方法中使用了递归,以此来注册全部的module)。咱们给出的实例通过ModuleCollection的收集以后变成了什么样子呢?
//this._modules简单结构示例 { root: { state: {}, _children: { cart: { ... }, products: { ... } } } }
收集完了以后,咱们看一下是如何初始化这些module的installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) { /* 是不是根module */ const isRoot = !path.length /* 获取module的namespace */ const namespace = store._modules.getNamespace(path) // register in namespace map /* 若是有namespace则在_modulesNamespaceMap中注册 */ if (module.namespaced) { store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { /* 获取父级的state */ const parentState = getNestedState(rootState, path.slice(0, -1))//深度取值,并返回取到的值 /* module的name */ const moduleName = path[path.length - 1] store._withCommit(() => {// 添加响应式属性 /* 将子module设置称响应式的 */ Vue.set(parentState, moduleName, module.state) }) } // 当前模块上下文信息 const local = module.context = makeLocalContext(store, namespace, path) /* 遍历注册mutation */ //mutation:key对应的handler module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) /* 遍历注册action */ module.forEachAction((action, key) => { const namespacedType = namespace + key registerAction(store, namespacedType, action, local) }) /* 遍历注册getter */ module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) /* 递归安装mudule */ module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) }
在这里构造了各个module的信息也就是localConext,包括各个模块的mutation,action ,getter ,mudule ,其中运用到了许多包装的技巧(主要为了计算模块的路径),还有代理的方式访问数据,详见注释
resetStoreVM
方法思路就是借助Vue响应式来实现Vuex的响应式
/* 经过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */ function resetStoreVM (store, state, hot) { /* 存放以前的vm对象 */ const oldVm = store._vm // bind store public getters store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} /* 经过Object.defineProperty为每个getter方法设置get方法,好比获取this.$store.getters.test的时候获取的是store._vm.test,也就是Vue对象的computed属性 */ // key = wrappedGetters的key,fn = wrappedGetters的key对应的value forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism computed[key] = () => fn(store) // store.getter并无像state同样在class直接注册了getter,setter,而是在这里定义的 // 用过代理的方式借助Vue计算属性实现了Vuex的计算属性 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的目的是在new一个Vue实例的过程当中不会报出一切警告 */ Vue.config.silent = true /* 这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/ //经过Vue的数据劫持,创造了dep,在Vue实例中使用的话Watcher会收集依赖,以达到响应式的目的 store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent // enable strict mode for new vm /* 使能严格模式,保证修改store只能经过mutation */ if (store.strict) { enableStrictMode(store) } if (oldVm) { /* 解除旧vm的state的引用,以及销毁旧的Vue对象 */ 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()) } }
在这里只要是把Vuex也构造为响应式的,store._vm指向一个Vue的实例,借助Vue数据劫持,创造了dep,在组件实例中使用的话Watcher会收集依赖,以达到响应式的目的。
至此,构造函数部分已通过了一遍了。
本文主要是学习了Vuex的初始化部分,实际的Vuex的api接口的实现也有相关的中文注释,我已经把主要部分中文注释代码放在这里,需者自取中文注释代码
学习过程当中参考了许多大神的文章,一并感谢。
Vue.js 源码解析(参考了大神的许多注释,大神写的太详尽了,我只补充了一部分)
Vuex框架原理与源码分析