Vuex

我们在使用Vue.js开发复杂的应用时,经常会遇到多个组件共享同一个状态,亦或是多个组件会去更新同一个状态,在应用代码量较少的时候,我们可以组件间通信去维护修改数据,或者是通过事件总线来进行数据的传递以及修改。但是当应用逐渐庞大以后,代码就会变得难以维护,从父组件开始通过prop传递多层嵌套的数据由于层级过深而显得异常脆弱,而事件总线也会因为组件的增多、代码量的增大而显得交互错综复杂,难以捋清其中的传递关系。

那么为什么我们不能将数据层与组件层抽离开来呢?把数据层放到全局形成一个单一的Store,组件层变得更薄,专门用来进行数据的展示及操作。所有数据的变更都需要经过全局的Store来进行,形成一个单向数据流,使数据变化变得“可预测”。

Vuex是一个专门为Vue.js框架设计的、用于对Vue.js应用程序进行状态管理的库,它借鉴了Flux、redux的基本思想,将共享的数据抽离到全局,以一个单例存放,同时利用Vue.js的响应式机制来进行高效的状态管理与更新。正是因为Vuex使用了Vue.js内部的“响应式机制”,所以Vuex是一个专门为Vue.js设计并与之高度契合的框架(优点是更加简洁高效,缺点是只能跟Vue.js搭配使用)。具体使用方法及API可以参考Vuex的官网

先来看一下这张Vuex的数据流程图,熟悉Vuex使用的同学应该已经有所了解。

Vuex源码解析 - 图1

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,所有修改State的操作必须通过Mutation进行,Mutation的同时提供了订阅者模式供外部插件调用获取State数据的更新。所有异步接口需要走Action,常见于调用后端接口异步获取更新数据,而Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。Vuex运行依赖Vue内部数据双向绑定机制,需要new一个Vue对象来实现“响应式化”,所以Vuex是一个专门为Vue.js设计的状态管理库。

安装

使用过Vuex的朋友一定知道,Vuex的安装十分简单,只需要提供一个store,然后执行下面两句代码即完成的Vuex的引入。

  1. Vue.use(Vuex);
  2. /*将store放入Vue创建时的option中*/
  3. new Vue({
  4. el: '#app',
  5. store
  6. });

那么问题来了,Vuex是怎样把store注入到Vue实例中去的呢?

Vue.js提供了Vue.use方法用来给Vue.js安装插件,内部通过调用插件的install方法(当插件是一个对象的时候)来进行插件的安装。

我们来看一下Vuex的install实现。

  1. /*暴露给外部的插件install方法,供Vue.use调用安装插件*/
  2. export function install (_Vue) {
  3. if (Vue) {
  4. /*避免重复安装(Vue.use内部也会检测一次是否重复安装同一个插件)*/
  5. if (process.env.NODE_ENV !== 'production') {
  6. console.error(
  7. '[vuex] already installed. Vue.use(Vuex) should be called only once.'
  8. )
  9. }
  10. return
  11. }
  12. /*保存Vue,同时用于检测是否重复安装*/
  13. Vue = _Vue
  14. /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
  15. applyMixin(Vue)
  16. }

这段install代码做了两件事情,一件是防止Vuex被重复安装,另一件是执行applyMixin,目的是执行vuexInit方法初始化Vuex。Vuex针对Vue1.0与2.0分别进行了不同的处理,如果是Vue1.0,Vuex会将vuexInit方法放入Vue的_init方法中,而对于Vue2.0,则会将vuexinit混淆进Vue的beforeCreate钩子中。来看一下vuexInit的代码。

  1. /*Vuex的init钩子,会存入每一个Vue实例等钩子列表*/
  2. function vuexInit () {
  3. const options = this.$options
  4. // store injection
  5. if (options.store) {
  6. /*存在store其实代表的就是Root节点,直接执行store(function时)或者使用store(非function)*/
  7. this.$store = typeof options.store === 'function'
  8. ? options.store()
  9. : options.store
  10. } else if (options.parent && options.parent.$store) {
  11. /*子组件直接从父组件中获取$store,这样就保证了所有组件都公用了全局的同一份store*/
  12. this.$store = options.parent.$store
  13. }
  14. }

vuexInit会尝试从options中获取store,如果当前组件是根组件(Root节点),则options中会存在store,直接获取赋值给Vuex源码解析 - 图2store引用。这样一来,所有的组件都获取到了同一份内存地址的Store实例,于是我们可以在每一个组件中通过this.$store愉快地访问全局的Store实例了。

那么,什么是Store实例?

Store

我们传入到根组件的store,就是Store实例,用Vuex提供的Store方法构造。

  1. export default new Vuex.Store({
  2. strict: true,
  3. modules: {
  4. moduleA,
  5. moduleB
  6. }
  7. });

我们来看一下Store的实现。首先是构造函数。

  1. constructor (options = {}) {
  2. // Auto install if it is not done yet and `window` has `Vue`.
  3. // To allow users to avoid auto-installation in some cases,
  4. // this code should be placed here. See #731
  5. /*
  6. 在浏览器环境下,如果插件还未安装(!Vue即判断是否未安装),则它会自动安装。
  7. 它允许用户在某些情况下避免自动安装。
  8. */
  9. if (!Vue && typeof window !== 'undefined' && window.Vue) {
  10. install(window.Vue)
  11. }
  12. if (process.env.NODE_ENV !== 'production') {
  13. assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  14. assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  15. assert(this instanceof Store, `Store must be called with the new operator.`)
  16. }
  17. const {
  18. /*一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数,可以监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)*/
  19. plugins = [],
  20. /*使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/
  21. strict = false
  22. } = options
  23. /*从option中取出state,如果state是function则执行,最终得到一个对象*/
  24. let {
  25. state = {}
  26. } = options
  27. if (typeof state === 'function') {
  28. state = state()
  29. }
  30. // store internal state
  31. /* 用来判断严格模式下是否是用mutation修改state的 */
  32. this._committing = false
  33. /* 存放action */
  34. this._actions = Object.create(null)
  35. /* 存放mutation */
  36. this._mutations = Object.create(null)
  37. /* 存放getter */
  38. this._wrappedGetters = Object.create(null)
  39. /* module收集器 */
  40. this._modules = new ModuleCollection(options)
  41. /* 根据namespace存放module */
  42. this._modulesNamespaceMap = Object.create(null)
  43. /* 存放订阅者 */
  44. this._subscribers = []
  45. /* 用以实现Watch的Vue实例 */
  46. this._watcherVM = new Vue()
  47. // bind commit and dispatch to self
  48. /*将dispatch与commit调用的this绑定为store对象本身,否则在组件内部this.dispatch时的this会指向组件的vm*/
  49. const store = this
  50. const { dispatch, commit } = this
  51. /* 为dispatch与commit绑定this(Store实例本身) */
  52. this.dispatch = function boundDispatch (type, payload) {
  53. return dispatch.call(store, type, payload)
  54. }
  55. this.commit = function boundCommit (type, payload, options) {
  56. return commit.call(store, type, payload, options)
  57. }
  58. // strict mode
  59. /*严格模式(使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误)*/
  60. this.strict = strict
  61. // init root module.
  62. // this also recursively registers all sub-modules
  63. // and collects all module getters inside this._wrappedGetters
  64. /*初始化根module,这也同时递归注册了所有子module,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才独有保存的Module对象*/
  65. installModule(this, state, [], this._modules.root)
  66. // initialize the store vm, which is responsible for the reactivity
  67. // (also registers _wrappedGetters as computed properties)
  68. /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
  69. resetStoreVM(this, state)
  70. // apply plugins
  71. /* 调用插件 */
  72. plugins.forEach(plugin => plugin(this))
  73. /* devtool插件 */
  74. if (Vue.config.devtools) {
  75. devtoolPlugin(this)
  76. }
  77. }

Store的构造类除了初始化一些内部变量以外,主要执行了installModule(初始化module)以及resetStoreVM(通过VM使store“响应式”)。

installModule

installModule的作用主要是为module加上namespace名字空间(如果有)后,注册mutation、action以及getter,同时递归安装所有子module。

  1. /*初始化module*/
  2. function installModule (store, rootState, path, module, hot) {
  3. /* 是否是根module */
  4. const isRoot = !path.length
  5. /* 获取module的namespace */
  6. const namespace = store._modules.getNamespace(path)
  7. // register in namespace map
  8. /* 如果有namespace则在_modulesNamespaceMap中注册 */
  9. if (module.namespaced) {
  10. store._modulesNamespaceMap[namespace] = module
  11. }
  12. // set state
  13. if (!isRoot && !hot) {
  14. /* 获取父级的state */
  15. const parentState = getNestedState(rootState, path.slice(0, -1))
  16. /* module的name */
  17. const moduleName = path[path.length - 1]
  18. store.`_withCommit`(() => {
  19. /* 将子module设成响应式的 */
  20. Vue.set(parentState, moduleName, module.state)
  21. })
  22. }
  23. const local = module.context = makeLocalContext(store, namespace, path)
  24. /* 遍历注册mutation */
  25. module.forEachMutation((mutation, key) => {
  26. const namespacedType = namespace + key
  27. registerMutation(store, namespacedType, mutation, local)
  28. })
  29. /* 遍历注册action */
  30. module.forEachAction((action, key) => {
  31. const namespacedType = namespace + key
  32. registerAction(store, namespacedType, action, local)
  33. })
  34. /* 遍历注册getter */
  35. module.forEachGetter((getter, key) => {
  36. const namespacedType = namespace + key
  37. registerGetter(store, namespacedType, getter, local)
  38. })
  39. /* 递归安装mudule */
  40. module.forEachChild((child, key) => {
  41. installModule(store, rootState, path.concat(key), child, hot)
  42. })
  43. }

resetStoreVM

在说resetStoreVM之前,先来看一个小demo。

  1. let globalData = {
  2. d: 'hello world'
  3. };
  4. new Vue({
  5. data () {
  6. return {
  7. $$state: {
  8. globalData
  9. }
  10. }
  11. }
  12. });
  13. /* modify */
  14. setTimeout(() => {
  15. globalData.d = 'hi~';
  16. }, 1000);
  17. Vue.prototype.globalData = globalData;
  18. /* 任意模板中 */
  19. <div>{{globalData.d}}</div>

上述代码在全局有一个globalData,它被传入一个Vue对象的data中,之后在任意Vue模板中对该变量进行展示,因为此时globalData已经在Vue的prototype上了所以直接通过this.prototype访问,也就是在模板中的{{prototype.d}}。此时,setTimeout在1s之后将globalData.d进行修改,我们发现模板中的globalData.d发生了变化。其实上述部分就是Vuex依赖Vue核心实现数据的“响应式化”。

不熟悉Vue.js响应式原理的同学可以通过笔者另一篇文章响应式原理了解Vue.js是如何进行数据双向绑定的。

接着来看代码。

  1. /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
  2. function resetStoreVM (store, state, hot) {
  3. /* 存放之前的vm对象 */
  4. const oldVm = store._vm
  5. // bind store public getters
  6. store.getters = {}
  7. const wrappedGetters = store._wrappedGetters
  8. const computed = {}
  9. /* 通过Object.defineProperty为每一个getter方法设置get方法,比如获取this.$store.getters.test的时候获取的是store._vm.test,也就是Vue对象的computed属性 */
  10. forEachValue(wrappedGetters, (fn, key) => {
  11. // use computed to leverage its lazy-caching mechanism
  12. computed[key] = () => fn(store)
  13. Object.defineProperty(store.getters, key, {
  14. get: () => store._vm[key],
  15. enumerable: true // for local getters
  16. })
  17. })
  18. // use a Vue instance to store the state tree
  19. // suppress warnings just in case the user has added
  20. // some funky global mixins
  21. const silent = Vue.config.silent
  22. /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告 */
  23. Vue.config.silent = true
  24. /* 这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/
  25. store._vm = new Vue({
  26. data: {
  27. $$state: state
  28. },
  29. computed
  30. })
  31. Vue.config.silent = silent
  32. // enable strict mode for new vm
  33. /* 使能严格模式,保证修改store只能通过mutation */
  34. if (store.strict) {
  35. enableStrictMode(store)
  36. }
  37. if (oldVm) {
  38. /* 解除旧vm的state的引用,以及销毁旧的Vue对象 */
  39. if (hot) {
  40. // dispatch changes in all subscribed watchers
  41. // to force getter re-evaluation for hot reloading.
  42. store._withCommit(() => {
  43. oldVm._data.$$state = null
  44. })
  45. }
  46. Vue.nextTick(() => oldVm.$destroy())
  47. }
  48. }

resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每一个getter绑定上get方法,这样我们就可以在组件里访问this.$store.getters.test就等同于访问store._vm.test。

  1. forEachValue(wrappedGetters, (fn, key) => {
  2. // use computed to leverage its lazy-caching mechanism
  3. computed[key] = () => fn(store)
  4. Object.defineProperty(store.getters, key, {
  5. get: () => store._vm[key],
  6. enumerable: true // for local getters
  7. })
  8. })

之后Vuex采用了new一个Vue对象来实现数据的“响应式化”,运用Vue.js内部提供的数据双向绑定功能来实现store的数据与视图的同步更新。

  1. store._vm = new Vue({
  2. data: {
  3. $$state: state
  4. },
  5. computed
  6. })

这时候我们访问store._vm.test也就访问了Vue实例中的属性。

这两步执行完以后,我们就可以通过this.$store.getter.test访问vm中的test属性了。

严格模式

Vuex的Store构造类的option有一个strict的参数,可以控制Vuex执行严格模式,严格模式下,所有修改state的操作必须通过mutation实现,否则会抛出错误。

  1. /* 使能严格模式 */
  2. function enableStrictMode (store) {
  3. store._vm.$watch(function () { return this._data.$$state }, () => {
  4. if (process.env.NODE_ENV !== 'production') {
  5. /* 检测store中的_committing的值,如果是false代表不是通过mutation的方法修改的 */
  6. assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
  7. }
  8. }, { deep: true, sync: true })
  9. }

首先,在严格模式下,Vuex会利用vm的Vuex源码解析 - 图3$state,也就是Store的state,在它被修改的时候进入回调。我们发现,回调中只有一句话,用assert断言来检测store._committing,当store._committing为false的时候会触发断言,抛出异常。

我们发现,Store的commit方法中,执行mutation的语句是这样的。

  1. this._withCommit(() => {
  2. entry.forEach(function commitIterator (handler) {
  3. handler(payload)
  4. })
  5. })

再来看看_withCommit的实现。

  1. _withCommit (fn) {
  2. /* 调用withCommit修改state的值时会将store的committing值置为true,内部会有断言检查该值,在严格模式下只允许使用mutation来修改store中的值,而不允许直接修改store的数值 */
  3. const committing = this._committing
  4. this._committing = true
  5. fn()
  6. this._committing = committing
  7. }

我们发现,通过commit(mutation)修改state数据的时候,会在调用mutation方法之前将committing置为true,接下来再通过mutation函数修改state中的数据,这时候触发Vuex源码解析 - 图4watch的回调执行断言,这时committing为false,则会抛出异常。这就是Vuex的严格模式的实现。

接下来我们来看看Store提供的一些API。

commit(mutation

  1. /* 调用mutation的commit方法 */
  2. commit (_type, _payload, _options) {
  3. // check object-style commit
  4. /* 校验参数 */
  5. const {
  6. type,
  7. payload,
  8. options
  9. } = unifyObjectStyle(_type, _payload, _options)
  10. const mutation = { type, payload }
  11. /* 取出type对应的mutation的方法 */
  12. const entry = this._mutations[type]
  13. if (!entry) {
  14. if (process.env.NODE_ENV !== 'production') {
  15. console.error(`[vuex] unknown mutation type: ${type}`)
  16. }
  17. return
  18. }
  19. /* 执行mutation中的所有方法 */
  20. this._withCommit(() => {
  21. entry.forEach(function commitIterator (handler) {
  22. handler(payload)
  23. })
  24. })
  25. /* 通知所有订阅者 */
  26. this._subscribers.forEach(sub => sub(mutation, this.state))
  27. if (
  28. process.env.NODE_ENV !== 'production' &&
  29. options && options.silent
  30. ) {
  31. console.warn(
  32. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  33. 'Use the filter functionality in the vue-devtools'
  34. )
  35. }
  36. }

commit方法会根据type找到并调用_mutations中的所有type对应的mutation方法,所以当没有namespace的时候,commit方法会触发所有module中的mutation方法。再执行完所有的mutation之后会执行_subscribers中的所有订阅者。我们来看一下_subscribers是什么。

Store给外部提供了一个subscribe方法,用以注册一个订阅函数,会push到Store实例的_subscribers中,同时返回一个从_subscribers中注销该订阅者的方法。

  1. /* 注册一个订阅函数,返回取消订阅的函数 */
  2. subscribe (fn) {
  3. const subs = this._subscribers
  4. if (subs.indexOf(fn) < 0) {
  5. subs.push(fn)
  6. }
  7. return () => {
  8. const i = subs.indexOf(fn)
  9. if (i > -1) {
  10. subs.splice(i, 1)
  11. }
  12. }
  13. }

在commit结束以后则会调用这些_subscribers中的订阅者,这个订阅者模式提供给外部一个监视state变化的可能。state通过mutation改变时,可以有效补获这些变化。

dispatch(action

来看一下dispatch的实现。

  1. /* 调用action的dispatch方法 */
  2. dispatch (_type, _payload) {
  3. // check object-style dispatch
  4. const {
  5. type,
  6. payload
  7. } = unifyObjectStyle(_type, _payload)
  8. /* actions中取出type对应的action */
  9. const entry = this._actions[type]
  10. if (!entry) {
  11. if (process.env.NODE_ENV !== 'production') {
  12. console.error(`[vuex] unknown action type: ${type}`)
  13. }
  14. return
  15. }
  16. /* 是数组则包装Promise形成一个新的Promise,只有一个则直接返回第0个 */
  17. return entry.length > 1
  18. ? Promise.all(entry.map(handler => handler(payload)))
  19. : entry[0](payload)
  20. }

以及registerAction时候做的事情。

  1. /* 遍历注册action */
  2. function registerAction (store, type, handler, local) {
  3. /* 取出type对应的action */
  4. const entry = store._actions[type] || (store._actions[type] = [])
  5. entry.push(function wrappedActionHandler (payload, cb) {
  6. let res = handler.call(store, {
  7. dispatch: local.dispatch,
  8. commit: local.commit,
  9. getters: local.getters,
  10. state: local.state,
  11. rootGetters: store.getters,
  12. rootState: store.state
  13. }, payload, cb)
  14. /* 判断是否是Promise */
  15. if (!isPromise(res)) {
  16. /* 不是Promise对象的时候转化称Promise对象 */
  17. res = Promise.resolve(res)
  18. }
  19. if (store._devtoolHook) {
  20. /* 存在devtool插件的时候触发vuex的error给devtool */
  21. return res.catch(err => {
  22. store._devtoolHook.emit('vuex:error', err)
  23. throw err
  24. })
  25. } else {
  26. return res
  27. }
  28. })
  29. }

因为registerAction的时候将push进_actions的action进行了一层封装(wrappedActionHandler),所以我们在进行dispatch的第一个参数中获取state、commit等方法。之后,执行结果res会被进行判断是否是Promise,不是则会进行一层封装,将其转化成Promise对象。dispatch时则从_actions中取出,只有一个的时候直接返回,否则用Promise.all处理再返回。

watch

  1. /* 观察一个getter方法 */
  2. watch (getter, cb, options) {
  3. if (process.env.NODE_ENV !== 'production') {
  4. assert(typeof getter === 'function', `store.watch only accepts a function.`)
  5. }
  6. return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  7. }

熟悉Vue的朋友应该很熟悉watch这个方法。这里采用了比较巧妙的设计,_watcherVM是一个Vue的实例,所以watch就可以直接采用了Vue内部的watch特性提供了一种观察数据getter变动的方法。

registerModule

  1. /* 注册一个动态module,当业务进行异步加载的时候,可以通过该接口进行注册动态module */
  2. registerModule (path, rawModule) {
  3. /* 转化称Array */
  4. if (typeof path === 'string') path = [path]
  5. if (process.env.NODE_ENV !== 'production') {
  6. assert(Array.isArray(path), `module path must be a string or an Array.`)
  7. assert(path.length > 0, 'cannot register the root module by using registerModule.')
  8. }
  9. /*注册*/
  10. this._modules.register(path, rawModule)
  11. /*初始化module*/
  12. installModule(this, this.state, path, this._modules.get(path))
  13. // reset store to update getters...
  14. /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
  15. resetStoreVM(this, this.state)
  16. }

registerModule用以注册一个动态模块,也就是在store创建以后再注册模块的时候用该接口。内部实现实际上也只有installModule与resetStoreVM两个步骤,前面已经讲过,这里不再累述。

unregisterModule

  1. /* 注销一个动态module */
  2. unregisterModule (path) {
  3. /* 转化称Array */
  4. if (typeof path === 'string') path = [path]
  5. if (process.env.NODE_ENV !== 'production') {
  6. assert(Array.isArray(path), `module path must be a string or an Array.`)
  7. }
  8. /*注销*/
  9. this._modules.unregister(path)
  10. this._withCommit(() => {
  11. /* 获取父级的state */
  12. const parentState = getNestedState(this.state, path.slice(0, -1))
  13. /* 从父级中删除 */
  14. Vue.delete(parentState, path[path.length - 1])
  15. })
  16. /* 重制store */
  17. resetStore(this)
  18. }

同样,与registerModule对应的方法unregisterModule,动态注销模块。实现方法是先从state中删除模块,然后用resetStore来重制store。

resetStore

  1. /* 重制store */
  2. function resetStore (store, hot) {
  3. store._actions = Object.create(null)
  4. store._mutations = Object.create(null)
  5. store._wrappedGetters = Object.create(null)
  6. store._modulesNamespaceMap = Object.create(null)
  7. const state = store.state
  8. // init all modules
  9. installModule(store, state, [], store._modules.root, true)
  10. // reset vm
  11. resetStoreVM(store, state, hot)
  12. }

这里的resetStore其实也就是将store中的_actions等进行初始化以后,重新执行installModule与resetStoreVM来初始化module以及用Vue特性使其“响应式化”,这跟构造函数中的是一致的。

插件

Vue提供了一个非常好用的插件Vue.js devtools

  1. /* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */
  2. const devtoolHook =
  3. typeof window !== 'undefined' &&
  4. window.__VUE_DEVTOOLS_GLOBAL_HOOK__
  5. export default function devtoolPlugin (store) {
  6. if (!devtoolHook) return
  7. /* devtoll插件实例存储在store的_devtoolHook上 */
  8. store._devtoolHook = devtoolHook
  9. /* 出发vuex的初始化事件,并将store的引用地址传给deltool插件,使插件获取store的实例 */
  10. devtoolHook.emit('vuex:init', store)
  11. /* 监听travel-to-state事件 */
  12. devtoolHook.on('vuex:travel-to-state', targetState => {
  13. /* 重制state */
  14. store.replaceState(targetState)
  15. })
  16. /* 订阅store的变化 */
  17. store.subscribe((mutation, state) => {
  18. devtoolHook.emit('vuex:mutation', mutation, state)
  19. })
  20. }

如果已经安装了该插件,则会在windows对象上暴露一个VUE_DEVTOOLS_GLOBAL_HOOK。devtoolHook用在初始化的时候会触发“vuex:init”事件通知插件,然后通过on方法监听“vuex:travel-to-state”事件来重置state。最后通过Store的subscribe方法来添加一个订阅者,在触发commit方法修改mutation数据以后,该订阅者会被通知,从而触发“vuex:mutation”事件。

最后

Vuex是一个非常优秀的库,代码量不多且结构清晰,非常适合研究学习其内部实现。最近的一系列源码阅读也使我自己受益匪浅,写这篇文章也希望可以帮助到更多想要学习探索Vuex内部实现原理的同学。