源码基本结构与install函数
# 一、源码基本结构
Vuex源码结构如下:
vuex
├── helpers.js #辅助函数
├── index.esm.js #esm版本入口文件
├── index.js #cjs版本入口文件
├── mixin.js #install函数使用的mixin
├── module #模块相关
│ ├── module-collection.js #模块集合
│ └── module.js #模块
├── plugins #插件相关
│ ├── devtool.js #devtool插件
│ └── logger.js #内置的logger插件
├── store.js #Store类
└── util.js #工具函数
2
3
4
5
6
7
8
9
10
11
12
13
helpers.js:辅助函数的定义,如mapState、mapGetters等辅助函数都是定义在该文件内index.esm.js:esm版本入口文件,导出Store类和install函数等内容index.js:cjs版本入口文件,导出Store类和install函数等内容mixin.js:install函数使用的mixin,执行install函数时,会给Vue实例混入mixin,其内容就定义在该文件module:模块相关的内容,Vuex模块的安装、定义、操作等等都定义在该目录下module-collection.js:模块集合,管理模块,管理模块的嵌套关系,管理模块的初始化,管理模块的注册,管理模块的更新等等module.js:模块,管理单个模块的属性、方法、状态等等
plugins:插件相关的内容,Vuex内置提供了devtool插件和logger插件store.js:Store类,Vuex的核心类,用于store的实例化、操作、访问等util.js:工具函数,如参数规范化、断言等等
# 二、install函数
在使用Vuex时,我们通常会在项目的入口文件里写如下的代码:
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store' // 一个使用Vuex.Store实例化的store实例
Vue.use(Vuex) // 安装Vuex插件
// 使用store
new Vue({
el: '#app',
store
})
2
3
4
5
6
7
8
9
10
11
12
由使用代码可知,Vuex本身是一个Vue插件。而我们都知道,Vue插件要么是一个具有install方法的对象,要么是一个函数,Vue.use方法会调用该函数或者对象的install方法进行安装。我们打开index.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
}
2
3
4
5
6
7
8
9
10
11
12
13
入口文件非常简单,主要做了以下几件事:
- 导入
Store类和install函数 - 导入辅助函数,如
mapState、mapGetters等辅助函数 - 将
Store类和install函数以及各种辅助函数导出,供外部使用
执行Vue.use(Vuex)时会执行导出的install方法,其定义在./store.js文件内,接下来,我们来看看install函数的实现。
// store.js
import applyMixin from './mixin'
// 此处省略了其它import导入
// 存放用户安装插件时的Vue构造函数,与Vuex关联
let Vue
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变量,完成Vuex和Vue的关联
Vue = _Vue
// 应用混合(mixin)到Vue实例中,以集成Vuex的功能
applyMixin(Vue)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
代码解析:
- 首先全局定义了一个
Vue变量,该变量由三个作用:- 作为Vuex和Vue的关联,在
install函数中,将传入的_Vue构造函数赋值给全局的Vue变量,完成Vuex和Vue的关联; - 作为Vuex的安装状态,在
install函数中,判断是否已经安装,如果已经安装,则直接返回,避免重复安装。
- 作为Vuex和Vue的关联,在
- 接着定义
install函数并导出,在install函数中- 如果
Vue变量已经存在,并且传入的_Vue实例与Vue变量相等,则说明已经安装,则直接返回,避免重复安装。此处需要满足两个条件才会认为已经安装,换句话说,Vuex可以在项目中被安装多次,但是同一个Vue构造函数只能安装一次。 - 将构造函数Vue赋值给全局的
Vue变量,完成Vuex和Vue的关联。 - 执行
applyMixin(Vue)函数,将Vuex混入到Vue实例中,以集成Vuex的功能。
- 如果
applyMixin函数定义在mixin.js文件中,其内容如下:
export default function (Vue) {
const version = Number(Vue.version.split('.')[0]) // 获取当前Vue的版本号
// vue2.0以上则混入beforeCreate钩子函数
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else { // vue1.x
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
// 重写Vue.prototype._init方法
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
function vuexInit() {
//...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
applyMixin会根据Vuex安装时传入的Vue构造函数获取当前使用的Vue的版本,并且根据不同的版本做不同的操作:在Vue2.0以上版本中,通过混入指向vuexInit函数的beforeCreate钩子函数,这样在Vue实例化时,所有的组件都会执行vuexInit函数,从而完成Vuex的安装;而在Vue2.0以下版本中,则是通过重写Vue.prototype._init方法的方式来实现Vuex的安装,其处理的步骤是首先保存原有的_init的方法,重写的方法中将vuexInit函数添加到options.init数组中,然后调用原有的_init方法,完成Vuex的安装。
接下来看看vuexInit函数的实现:
function vuexInit() {
// 获取当前实例的$options
const options = this.$options
// store injection
if (options.store) { // 如果有store属性,则注入
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
2
3
4
5
6
7
8
9
10
11
12
vuexInit函数做的事情就是将store实例注入到每一个vue实例中,其基本的思路是当前vue实例如果有store则直接注入,否则则从父级实例中获取store实例:
- 首先获取当前实例的store属性配置,
store = this.$options.store - 如果
store存在:
- 2.1 如果
store是一个函数,则将store()的执行返回值赋值给this.$store - 2.2 否则,将
this.$store = store如果不存在,则从父级实例中获取$store属性,如果存在,则赋值给this.$store,这确保所有的子组件都能够读取到store实例
之所以子组件可以通过父组件来获取$store是因为在Vue中,创建阶段是先父后子,而挂在阶段是先子后父,所以子组件在beforeCreate钩子中能够读取到父组件的$store属性。如下代码,如果打印出各个组件的生命周期,其结果为:App beforeCreated->App created->App beforeMount->Outer beforeCreated->Outer created->Outer beforeMount->Inner beforeCreated->Inner created->Inner beforeMount->Inner mounted->Outer mounted->App mounted
<APP>
<Outer>
<Inner />
</Outer>
</App>
2
3
4
5
# 三、总结
Vuex对外导出了Store类、install函数,以及mapState、mapMutations等辅助辅助函数。以下列代码为例:
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store' // 一个使用Vuex.Store实例化的store实例
Vue.use(Vuex) // 安装Vuex插件
// 使用store
new Vue({
el: '#app',
store
})
2
3
4
5
6
7
8
9
10
11
12
当执行以上代码时,Vue和Vuex做了以下几件事:
- Vue执行Vuex提供的
install函数,并将Vue构造函数传入,完成Vuex和Vue的关联。- 已安装,则直接返回,避免重复安装。
- 将构造函数Vue赋值给全局的
Vue变量,完成Vuex和Vue的关联。 - 执行
applyMixin(Vue)函数,在Vue2.x版本中混入beforeCreate钩子,在Vue2.0以下使用重写原型的方式修改init钩子,以集成Vuex的功能。
- 通过
new Store创建了一个store实例,并将store实例注入到Vue实例中。- Vue执行
beforeCreate钩子或者_init方法 - 当前组件实例有
store属性,则注入$store属性,否则从父级实例中获取$store实例属性。
- Vue执行
Vuex整体的结构如下图:

接下来,我们逐步解读下new Store(options)时,Vuex是如何处理的。