做为Vue全家桶之一的vuex,在Vue框架中起着很重要的角色。Vuex源码篇幅虽然很少,但解读起来仍是须要下一些功夫的。下面咱们就参照vuex官方指南文档内容进行分析。 vue
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 react
Vuex 能够帮助咱们管理共享状态,并附带了更多的概念和框架。这须要对短时间和长期效益进行权衡。
若是只是简单的单页应用,最好不要使用 Vuex,使用 Vuex 多是繁琐冗余的,一个简单的 store 模式就足够了。可是,若是构建一个中大型单页应用,须要考虑如何更好地在组件外部管理状态,Vuex 将会成为最佳选择。git
Vuex是一个专门为Vue.js框架设计的、用于对Vue.js应用程序进行状态管理的库,它借鉴了Flux、redux的基本思想,将共享的数据抽离到全局,以一个单例存放,同时利用Vue.js的响应式机制来进行高效的状态管理与更新。正是由于Vuex使用了Vue.js内部的“响应式机制”,因此Vuex是一个专门为Vue.js设计并与之高度契合的框架(优势是更加简洁高效,缺点是只能跟Vue.js搭配使用)。
vuex状态自管理应用包含如下几个部分:
state:驱动应用的数据源;
view:以声明方式将 state 映射到视图;
actions:响应在 view 上的用户输入致使的状态变化。
来看一下“单向数据流”理念的简单示意图:
github
vuex在项目中应用的目录,在src下建立store文件:vuex
├── store
│ ├── modules
│ ├── todos.js
│ ├── getter.js
│ ├── index.js
├── APP.vue
└── main.js
复制代码
index.jsredux
//index.js import Vue from 'vue'; import Vuex from 'vuex'; import getters from './getters' import app from './modules/todos' //Load Vuex Vue.use(Vuex) //create store export default new Vuex.Store({ modules: { app }, getters }) 复制代码
getters.jswindows
//getters.js const getters = { allTodos: (state) => state.app.todos, red: (state) => state.app.red, blue: (state) => state.app.blue } export default getters 复制代码
todos.js数组
// todos.js const app = { state: { todos: [{ id: 1, title: 'one' },{ id: 2, title: 'two' }], red: 0, blue: 0 }, mutations: { ADD_COUNT: (state) => { state.red++, state.blue++ }, MINUS_COUNT: (state)=> { state.red--, state.blue-- } }, actions: { addCounts: ({commit})=>{ commit('ADD_COUNT') }, minusCounts: ({commit})=>{ commit('MINUS_COUNT') } } } export default app 复制代码
main.js缓存
//main.js import store from './store' new Vue({ store, render: h => h(App), }).$mount('#app') 复制代码
APP.vuebash
import { mapGetters } from 'vuex' ...... export default { name: 'app', computed: mapGetters(['allTodos']) } 复制代码
子组件应用:
//组件应用 computed:{ red(){ return this.$store.state.app.red }, blue(){ return this.$store.state.app.blue } }, methods: { addOne() { this.$store.dispatch('addCounts') }, minusOne() { this.$store.dispatch('minusCounts') }, } 复制代码
先看下vuex官方指南文档,再结合上面的小实例对核心源码进行分析。
vuex官网指南:vuex.vuejs.org/zh/guide/
vuex源码:github.com/vuejs/vuex
git上下载的源码文件不少,可是核心执行源码存在/src里,源码文件很少,目录划分也很清晰,每一个文件都有着各自的功能:
├── module
│ ├── module-collection.js
│ ├── module.js
├── plugins // 插件
│ ├── devtool.js
│ ├── logger.js
├── helpers.js //辅助函数
├── index.esm.js
├── index.js //入口文件
├── mixin.js
├── store.js
└── util.js
复制代码
index.js做为入口文件,包含了全部的核心代码引用:
import { Store, install } from './store' import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } 复制代码
在实例的store文件夹的index.js里经过new store来注册store,下面是具体Store构造方法,能够结合代码注释看:
constructor (options = {}) { ...... // store internal state /* 用来判断严格模式下是不是用mutation修改state的 */ this._committing = false /* 存放action */ this._actions = Object.create(null) this._actionSubscribers = [] /* 存放mutation */ this._mutations = Object.create(null) /* 存放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 const state = this._modules.root.state /*初始化根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)) const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools if (useDevtools) { devtoolPlugin(this) } } 复制代码
在实例的子组件应用里会用到dispatch,dispatch的功能是触发并传递一些参数(payload)给对应 type 的 action。在dispatch 中,先进行参数的适配处理,而后判断 action type 是否存在,若存在就逐个执行。
/* 调用action的dispatch方法 */ dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } /* actions中取出type对应的ation */ 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) } } /* 是数组则包装Promise造成一个新的Promise,只有一个则直接返回第0个 */ 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 }) } 复制代码
commit方法和dispatch相比虽然都是触发type,可是对应的处理却相对复杂。先进行参数适配,判断触发mutation type,利用_withCommit方法执行本次批量触发mutation处理函数,并传入payload参数。执行完成后,通知全部_subscribers(订阅函数)本次操做的mutation对象以及当前的 state 状态,若是传入了已经移除的 silent 选项则进行提示警告。能够看实例里module的actions的属性。
/* 调用mutation的commit方法 */ commit (_type, _payload, _options) { // check object-style commit 校验参数 const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } /* 取出type对应的mutation的方法 */ const entry = this._mutations[type] if (!entry) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] unknown mutation type: ${type}`) } return } /* 执行mutation中的全部方法 */ 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' ) } } 复制代码
在实例module里state有常量和变量,能够结合源码来看状态的改变原理。_withCommit是一个代理方法,全部触发mutation的进行state修改的操做都通过它,由此来统一管理监控state状态的修改。缓存执行时的 committing 状态将当前状态设置为 true 后进行本次提交操做,待操做完毕后,将 committing 状态还原为以前的状态。
//保存以前的提交状态 _withCommit (fn) { /* 调用withCommit修改state的值时会将store的committing值置为true, 内部会有断言检查该值,在严格模式下只容许使用mutation来修改store中的值, 而不容许直接修改store的数值 */ const committing = this._committing this._committing = true fn() this._committing = committing } } 复制代码
在实例的module里用到了state、mutation、action,Store的构造类除了初始化一些内部变量之外,主要执行了installModule(初始化module)以及resetStoreVM(经过VM使store“响应式”)。 installModule的做用主要是用为module加上namespace名字空间(若是有)后,注册mutation、action以及getter,同时递归安装全部子module。最后执行plugin的植入。
/*初始化module*/ 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) { if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') { console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`) } 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 */ module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) /* 遍历注册action */ module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, 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) }) } 复制代码
registerModule用以注册一个动态模块,也就是在store建立之后再注册模块的时候用该接口。
/* 注册一个动态module,当业务进行异步加载的时候,能够经过该接口进行注册动态module */ registerModule (path, rawModule, options = {}) { /* 转化成Array */ if (typeof path === 'string') path = [path] if (process.env.NODE_ENV !== 'production') { 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) /*初始化module*/ installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... /* 经过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */ resetStoreVM(this, this.state) } 复制代码
一样,与registerModule对应的方法unregisterModule,动态注销模块。实现方法是先从state中删除模块,而后用resetStore来重制store
/* 注销一个动态module */ unregisterModule (path) { /* 转化成Array */ if (typeof path === 'string') path = [path] if (process.env.NODE_ENV !== 'production') { assert(Array.isArray(path), `module path must be a string or an Array.`) } /*注销*/ this._modules.unregister(path) this._withCommit(() => { /* 获取父级的state */ const parentState = getNestedState(this.state, path.slice(0, -1)) /* 从父级中删除 */ Vue.delete(parentState, path[path.length - 1]) }) /* 重制store */ resetStore(this) } 复制代码
能够看到实例将getters作成单独的一个getters.js文件进行getter状态管理。
执行完各 module 的 install 后,执行 resetStoreVM 方法,进行 store 组件的初始化,resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每个getter绑定上get方法:
/* 经过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */ function resetStoreVM (store, state, hot) { /* 存放以前的vm对象 */ const oldVm = store._vm // bind store public getters(computed) store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} /* 经过Object.defineProperty为每个getter方法设置get方法,好比获取this.$store.getters.test的时候获取的是store._vm.test,也就是Vue对象的computed属性 */ forEachValue(wrappedGetters, (fn, key) => { computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) const silent = Vue.config.silent /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程当中不会报出一切警告 */ Vue.config.silent = true /* 这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/ store._vm = new Vue({ data: { $$state: state }, computed }) // 恢复Vue的模式 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()) } } 复制代码
//注册getter function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== 'production') { 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 ) } } 复制代码
实例APP.vue里用到了mapGetters,经过computed计算属性获取state状态。当一个组件须要获取多个状态、多个getter、多个mutations方法、多个action分发事件时候,这些辅助函数帮助咱们生成计算属性,这四个辅助函数具体实现方法在helpers.js里:
export const mapState = normalizeNamespace((namespace, states) => { const res = {} normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState () { let state = this.$store.state let getters = this.$store.getters if (namespace) { const module = getModuleByNamespace(this.$store, 'mapState', namespace) if (!module) { return } state = module.context.state getters = module.context.getters } return typeof val === 'function' ? val.call(this, state, getters) : state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res }) ...... 复制代码
Vuex源码里提供了两个插件源码:devtool.js和logger.js。若是已经安装了Vue.js devtools,则会在windows对象上暴露一个VUE_DEVTOOLS_GLOBAL_HOOK。devtoolHook用在初始化的时候会触发“vuex:init”事件通知插件,而后经过on方法监听“vuex:travel-to-state”事件来重置state。最后经过Store的subscribe方法来添加一个订阅者,在触发commit方法修改mutation数据之后,该订阅者会被通知,从而触发“vuex:mutation”事件:
/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */ const target = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {} const devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__ export default function devtoolPlugin (store) { if (!devtoolHook) return /* devtool插件实例存储在store的_devtoolHook上 */ store._devtoolHook = devtoolHook // 触发Vuex组件初始化的hook,并将store的引用地址传给devtool插件,使插件获取store的实例 devtoolHook.emit('vuex:init', store) // 提供“时空穿梭”功能,即state操做的前进和倒退 devtoolHook.on('vuex:travel-to-state', targetState => { store.replaceState(targetState) }) // 订阅store的变化, mutation被执行时,触发hook,并提供被触发的mutation函数和当前的state状态 store.subscribe((mutation, state) => { devtoolHook.emit('vuex:mutation', mutation, state) }) } 复制代码
Vuex是一个很是优秀的库,代码简洁,逻辑清晰。源码中还有一些工具函数相似hotUpdate、watch 以及 subscribe等,感兴趣的小伙伴们有时间能够好好研究一下源码,了解了源码设计思想和实现逻辑后,会豁然开朗,应用起来也会游刃有余。