生产实践:Vuex中的单例模式

近年来,基于 Flux 架构的状态管理工具层出不穷,其中应用最广泛的要数 Redux 和 Vuex。无论是 Redux 和 Vuex,它们都实现了一个全局的 Store 用于存储应用的所有状态。这个 Store 的实现,正是单例模式的典型应用。这里我们以 Vuex 为例,研究一下单例模式是怎么发光发热的:

理解 Vuex 中的 Store

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 ——Vuex官方文档

在Vue中,组件之间是独立的,组件间通信最常用的办法是 props(限于父组件和子组件之间的通信),稍微复杂一点的(比如兄弟组件间的通信)我们通过自己实现简单的事件监听函数也能解决掉。
但当组件非常多、组件间关系复杂、且嵌套层级很深的时候,这种原始的通信方式会使我们的逻辑变得复杂难以维护。这时最好的做法是将共享的数据抽出来、放在全局,供组件们按照一定的的规则去存取数据,保证状态以一种可预测的方式发生变化。于是便有了 Vuex,这个用来存放共享数据的唯一数据源,就是 Store。
关于 Vuex 的细节,大家可以参考Vuex的官方文档,此处提及 Vuex,除了为了拓宽大家的知识面,更重要的是为了说明单例模式在生产实践中广泛的应用和不可或缺的地位。如果对 Vuex 没有兴趣,那么大家只需关注“一个 Vue 实例只能对应一个 Store”这一点即可。

Vuex如何确保Store的唯一性

我们先来看看如何在项目中引入 Vuex:

  1. // 安装vuex插件
  2. Vue.use(Vuex)
  3. // 将store注入到Vue实例中
  4. new Vue({
  5. el: '#app',
  6. store
  7. })

通过调用Vue.use()方法,我们安装了 Vuex 插件。Vuex 插件是一个对象,它在内部实现了一个 install 方法,这个方法会在插件安装时被调用,从而把 Store 注入到Vue实例里去。也就是说每 install 一次,都会尝试给 Vue 实例注入一个 Store。
在 install 方法里,有一段逻辑和我们楼上的 getInstance 非常相似的逻辑:

  1. let Vue // 这个Vue的作用和楼上的instance作用一样
  2. ...
  3. export function install (_Vue) {
  4. // 判断传入的Vue实例对象是否已经被install过Vuex插件(是否有了唯一的state)
  5. if (Vue && _Vue === Vue) {
  6. if (process.env.NODE_ENV !== 'production') {
  7. console.error(
  8. '[vuex] already installed. Vue.use(Vuex) should be called only once.'
  9. )
  10. }
  11. return
  12. }
  13. // 若没有,则为这个Vue实例对象install一个唯一的Vuex
  14. Vue = _Vue
  15. // 将Vuex的初始化逻辑写进Vue的钩子函数里
  16. applyMixin(Vue)
  17. }

楼上便是 Vuex 源码中单例模式的实现办法了,套路可以说和我们的getInstance如出一辙。通过这种方式,可以保证一个 Vue 实例(即一个 Vue 应用)只会被 install 一次 Vuex 插件,所以每个 Vue 实例只会拥有一个全局的 Store。