了解Vue.use方法原理对开发vue插件是很有帮助的,Vue.use方法是在初始化Vue全局API(initGlobalAPI)的时候挂到Vue上面的,代码如下:
/* @flow */
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
这个方法可以分为两步:
1.首先将Vue的_installedPlugins属性赋值给_installedPlugins变量,没有该属性的话则初始化一个空数组。有的话,则判断插件有没有被安装过,安装过则直接返回,避免重复安装。
2.args是存储额外传递的参数,也就是Vue.use(‘name’, args)的时候可以传递参数;第一个参数是this,也就是class Vue。接下来,判断插件有无install方法,有的话直接执行插件的install方法,在vuex的初始化过程中是有install方法的,代码如下:
Vue.use(Store)
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
applyMixin(Vue)
}
这段代码也判断是否已经安装过了vuex,安装过了则报出警告,否则将传入的_Vue赋值给变量Vue(第一次install的时候Vue为undefined),接下来执行applyMixin(),代码如下:
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
这段代码首先判断了vuex的版本,这里只分析2以上的版本,使用了Vue.mixin()在钩子函数beforeCreate中添加了vuexInit方法,在beforeCreate执行的时候触发,vuexInit保证了在所有的组件中都能通过this.$store来访问Store对象。那beforeCreate是如何加入Vue中的呢,接着看Vue.mixin的源码(其实Vue的mixin语法原理也是使用的Vue.mixin方法):
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
// 这儿对一些属性进行规范化
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
// 孩子如果有extends属性,则递归调用mergeOptions
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
// 孩子如果有mixins属性,则递归调用mergeOptions
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Vue.mixin中主要使用的是mergeOptions(合并配置)方法,mergeOptions中进行了一些属性的规范化(这里不多赘述),在vuex的初始化过程中主要关注:
for (key in parent) {
mergeField(key)
}
这个时候parent其实Vue上已经有了一个钩子函数beforeCreate(在Vue 实例化的过程中产生的),执行mergeField
/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*/
var strats = config.optionMergeStrategies;
LIFECYCLE_HOOKS.forEach(function (hook) {
strats[hook] = mergeHook;
});
function mergeHook (
parentVal,
childVal
) {
var res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
return res
? dedupeHooks(res)
: res
}
针对不同的属性,Vue有不同的合并策略,在vuex install的过程中只关注对于钩子函数的合并策略,因为传入的是Vue.mixin({ beforeCreate: vuexInit })。合并策略如上,在子元素有值且父元素有值的情况下,concat两个钩子函数,最终的beforeCreate其实是一个数组。在触发beforeCreate的过程中其实就是遍历了beforeCreate这个钩子数组,依次执行了数组中各个函数,(对于父子组件的钩子函数触发时机原理其实与此类似)。