这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
Vue.mixin
是一个使用恰当时会是一个非常优秀的API,滥用或者使用不当时,将会是一场噩梦,这也是官方不推荐在应用项目中使用的原因,虽然其存在一些弊端:命名冲突、来源混乱等,但是不妨碍我们对其原理进行探索的步伐
先看一下其如何使用,现在有这么一段代码
import Vue from "vue";
Vue.mixin({
created: function () {
console.log("mixins created");
},
});
new Vue({
created() {
console.log("实例 created");
},
});
复制代码
mixins
会将内部的created
和组件的created
进行合并,控制台会先后输出mixins created
、实例 created
Vue.mixin
上文调用initGlobalAPI
函数进行全局API初始化时,内部调用了initMixin(Vue)
对mixin
进行全局API注册,代码如下:省略无关代码
export function initGlobalAPI(Vue) {
// 整合所有的全局相关的内容
Vue.options = Object.create(null);
initMixin(Vue);
}
复制代码
注册全局mixin
代码如下:其内部做的事情并不多,合并选项的动作全部抽取到了mergeOptions
函数中
export function initMixin (Vue) {
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
export function mergeOptions(parent, child){
// todo...
}
复制代码
在使用mixin
过程中,本质是对于parent
和child
选项的合并,其实就是两个对象合并的一个过程
遍历parent
和child
// 遍历 parent
for (const key in parent) {
mergeField(key);
}
// 遍历 child
for (const key in child) {
// 如果已经合并过了, 就不需要再次合并了
if (!parent.hasOwnProperty(key)) {
mergeField(key);
}
}
复制代码
具体的合并策略封装在mergeField
函数中,在编写其逻辑之前先回顾一下mixin
对于同名选项时是以怎样的方式进行合并
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。比如生命周期
- 值为对象的选项,例如
methods
、components
和directives
,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
针对不同的情况mixin
会采用不同的合并手段处理,首先看一看对于值为对象的情况是如何合并,也就是默认的合并策略
function mergeField(key) {
// 都是对象
if (typeof parent[key] === "object" && typeof child[key] === "object") {
// 键名冲突,以child的覆盖parent,即取组件的
options[key] = {
...parent[key],
...child[key],
};
} else if (child[key] === null) {
// 以 parent 为准
options[key] = parent[key];
} else {
// 以 child 为准
options[key] = child[key];
}
}
复制代码
对于同名钩子合并,比如生命周期的合并,需要特殊处理
首先将全部的生命周期定义
const LIFECYCLE_HOOKS = [
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"beforeDestroy",
"destroyed",
];
复制代码
生命周期钩子的合并是一样的,因此可以遍历LIFECYCLE_HOOKS
定义每个钩子的行为
function mergeHook(parentVal, childVal) {
const res = childVal
? parentVal
? parentVal.concat(childVal) // parent child 都有, 需要变成数组
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal; // 有child 直接使用老的
return res ? dedupeHooks(res) : res;
}
/**
* 删除重复的数据
*/
function dedupeHooks(hooks) {
const res = [];
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i]);
}
}
return res;
}
// 遍历全部的生命周期钩子
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
复制代码
在Vue.mixin
中,对于components
、directives
、filters
等选项都有进行特殊的处理