# Vuex源码阅读笔记

Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。有些同学可能会问,那我定义一个全局对象,再去上层封装了一些数据存取的接口不也可以么?

Vuex 和单纯的全局对象有以下两点不同:

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。

# Vuex 初始化

当我们在代码中通过 import Vuex from 'vuex' 的时候,实际上引用的是一个对象。

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}
1
2
3
4
5
6
7
8
9
10

和 Vue-Router 一样,Vuex 也同样存在一个静态的 install 方法,install 的逻辑很简单,把传入的 _Vue 赋值给 Vue 并执行了 applyMixin(Vue) 方法。

applyMixin 就是这个 export default function,它还兼容了 Vue 1.0 的版本,这里我们只关注 Vue 2.0 以上版本的逻辑,它其实就全局混入了一个 beforeCreate 钩子函数,它的实现非常简单,就是把 options.store 保存在所有组件的 this.$store 中,这个 options.store 就是我们在实例化 Store 对象的实例,稍后我们会介绍,这也是为什么我们在组件中可以通过 this.$store 访问到这个实例。

Store 实例化

我们在 import Vuex 之后,会实例化其中的 Store 对象,返回 store 实例并传入 new Vue 的 options 中,也就是我们刚才提到的 options.store 。

我们把 Store 的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm 。

初始化模块

模块对于 Vuex 的意义:

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块——从上至下进行同样方式的分割。

所以从数据结构上来看,模块的设计就是一个树型结构,store 本身可以理解为一个 root module,它下面的 modules 就是子模块。

对于 root module 的下一层 modules 来说,它们的 parent 就是 root module,那么他们就会被添加的 root module 的 _children 中。每个子模块通过路径找到它的父模块,然后通过父模块的 addChild 方法建立父子关系,递归执行这样的过程,最终就建立一颗完整的模块树。

安装模块

初始化模块后,执行安装模块的相关逻辑,它的目标就是对模块中的 state、getters、mutations、actions 做初始化工作,它的入口代码是:

const state = this._modules.root.state
installModule(this, state, [], this._modules.root)
1
2

installModule 实际上就是完成了模块下的 state、getters、actions、mutations 的初始化工作,并且通过递归遍历的方式,就完成了所有子模块的安装工作。

初始化 store._vm

Store 实例化的最后一步,就是执行初始化 store._vm 的逻辑,它的入口代码是:

resetStoreVM(this, state)
1

resetStoreVM 的作用实际上是想建立 getters 和 state 的联系,因为从设计上 getters 的获取就依赖了 state ,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 computed 计算属性来实现。

总结

我们要把 store 想象成一个数据仓库,为了更方便的管理仓库,我们把一个大的 store 拆成一些 modules,整个 modules 是一个树型结构。

每个 module 又分别定义了 state,getters,mutations、actions,我们也通过递归遍历模块的方式都完成了它们的初始化。

为了 module 具有更高的封装度和复用性,还定义了 namespace 的概念。最后我们还定义了一个内部的 Vue 实例,用来建立 state 到 getters 的联系,并且可以在严格模式下监测 state 的变化是不是来自外部,确保改变 state 的唯一途径就是显式地提交 mutation。

这一节我们已经建立好 store,接下来就是对外提供了一些 API 方便我们对这个 store 做数据存取的操作,下一节我们就来从源码角度来分析 Vuex 提供的一系列 API。

# API

Vuex 提供的一些常用 API 包括数据的存取、语法糖、模块的动态更新等。

要理解 Vuex 提供这些 API 都是方便我们在对 store 做各种操作来完成各种能力,尤其是 mapXXX 的设计,让我们在使用 API 的时候更加方便,这也是我们今后在设计一些 JavaScript 库的时候,从 API 设计角度中应该学习的方向。

# 插件

Vuex 除了提供的存取能力,还提供了一种插件能力,让我们可以监控 store 的变化过程来做一些事情。

Vuex 的 store 接受 plugins 选项,我们在实例化 Store 的时候可以传入插件,它是一个数组,然后在执行 Store 构造函数的时候,会执行这些插件:

const {
  plugins = [],
  strict = false
} = options
// apply plugins
plugins.forEach(plugin => plugin(this))
1
2
3
4
5
6

在我们实际项目中,我们用到的最多的就是 Vuex 内置的 Logger 插件,它能够帮我们追踪 state 变化,然后输出一些格式化日志。当然我们也可以自己去实现 Vuex 的插件,来帮助我们实现一些特定的需求。