Vuex3源码学习笔记之Vuex的安装

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 先查看下 src 下面的文件目录结构。

目录
  1. 源码结构
  2. 插件安装
  3. 参考资料

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

源码结构#

先查看下 src 下面的文件目录结构。

src
├── module
	  ├── module-collection.js  # 模块收集类 ModuleCollection
	  ├── module.js             # 模块类 Module
├── plugins
	  ├── devtool.js            # devtool 插件
	  ├── logger.js             # logger 插件
├── helpers.js                # 辅助函数,定义 mapXXX 语法糖
├── index.esm.js              # 模块 esm 入口
├── index.js                  # 入口
├── mixin.js                  # 混入到 Vue 的函数 applyMixin
├── store.js                  # 仓库类 Store
├── 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.js文件中导入仓库类Store和安装方法install,从helpers.js文件中导入我们常用的语法糖mapXXX和一个绑定命名空间的辅助函数createNamespacedHelpers,然后再把这些属性方法导出去供用户使用。

插件安装#

先看下 Vuex 的怎么安装在 Vue 上的,我们平时是这样使用 Vuex 的。

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

可以看出是通过 Vue 的use方法来安装 Vuex 的,这和安装 Vue 的插件是一样的方法。看过 Vue 的源码的同学都知道,使用use方法安装插件时,会在方法里面调用插件自带的install方法把插件安装到 Vue 中。

下面我们看下 Vuex 的install方法的代码,在store.js文件的最后面。

let Vue; // bind on install
 
// ! Vuex 安装方法
export function install(_Vue) {
  // ! 插件只安装一次(单例模式)
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== "production") {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      );
    }
    return;
  }
  Vue = _Vue; // ! 传入的 _Vue 赋值给全局变量 Vue,也用来检测是否重复安装
  applyMixin(Vue); // ! 把 vuexInit 方法混入到 Vue 的 beforeCreated 钩子函数中
}

一般安装 Vue 的插件都需要把 Vue 作为参数传入,因为插件是安装在 Vue 上的,Vuex 也不例外。首先声明了全局变量Vue存储 Vue 这个库,在安装的时候会把传入的_Vue存储到Vue变量中,这样下次查询一下全局变量存储的库是否和传入的库相同,如果相同,说明已经安装过这个插件了,直接返回。也就是说 Vuex 只能安装一次,不能重复安装,这是一个单例模式的设计方案。然后在最后调用applyMixin函数,同时把变量Vue传入。

这里主要是通过applyMixin函数来安装 Vuex。

下面看下applyMixin函数的代码,在mixin.js文件中

export default function (Vue) {
  const version = Number(Vue.version.split(".")[0]); // ! 获取主版本号
 
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit }); // ! 使用 Vue 的 mixin 方法把 vuexInit 混入到 beforeCreate 钩子中
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // ! 兼容 v1.0 版本
    const _init = Vue.prototype._init;
    Vue.prototype._init = function (options = {}) {
      options.init = options.init ? [vuexInit].concat(options.init) : vuexInit;
      _init.call(this, options);
    };
  }
  // ...
}

先看前面的代码,声明变量version存储获取的 Vue 的主版本号。即版本号中第一个点前面的数字,比如版本是 2.6.10,获取的值就是 2。然后根据主版本号使用不同的安装方法,这里兼容了 Vue 1.0 的安装方法,先不管它。

看下 2.0 以上版本的安装方法,调用 Vue 的mixin方法,在beforeCreate生命周期中混入一个vuexInit函数,也就是说在 Vue 初始化实例的时候,也会初始化 Vuex。

下面看下vuexInit函数的代码,它在applyMixin函数的尾部,了解 Vuex 是如何初始化的。

// ! Vuex 初始化方法
function vuexInit() {
  const options = this.$options; // ! 获取 Vue 实例的选项
 
  // store injection
  if (options.store) {
    // ! 把用户定义的 store 实例赋值给 Vue 实例的 $store 属性,方便组件操作 store
    this.$store =
      typeof options.store === "function" ? options.store() : options.store;
  } else if (options.parent && options.parent.$store) {
    this.$store = options.parent.$store; // ! 从父组件中获取 store,因为全局的 store 都是同一个
  }
}

首先声明常量options存储 Vue 实例初始化的选项,然后判断选项中是否有store属性,这个属性就是使用 Vuex 时创建的 Store 实例对象,这个实例对象会传入到 Vue 的选项中。

如下面代码所示:

import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";
Vue.use(Vuex);
 
const store = new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
});
 
new Vue({
  el: "#app",
  store, // 放入到 Vue 的 options 中
  render: (h) => h(App),
});

先判断options.store的类型,如果是一个函数,需要调用它生成一个Store实例对象,如果是对象则直接赋值。

什么时候会使用函数类型呢?在我们构建 Vue 的服务端渲染时会使用,通过函数生成一个Store实例对象,这样每次都会生成一个新的实例。

const store = () =>
  new Vuex.Store({
    state: {},
    getters: {},
    mutations: {},
    actions: {},
  });

然后如果没有options.store,则会从它的父级组件中去寻找,判断父级中是否已经存在$store属性,如果存在,把它赋值到自身的$store属性中,因为 Vuex 的设计模式是一个单例模式,全局的store都是同一个的,也是唯一的一个,全部组件都共享这个属性,所以从父级中获取也是一样的。

这样,Vuex 的安装过程就完成了。

参考资料#