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 的安装过程就完成了。