源码基本结构与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是如何处理的。