Vue2核心原理(简易版)-Mixin混入
什么是Mixin-混入?
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。文档地址
简单来说,可以把mixin看成是一种vue-style的组合,一个mixin对象包含vue实例的所有组件选项,并且各个选项将按照一定的规则进行合并。从而实现了逻辑复用,达到精简代码的目的。
举例:
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
我们甚至可以全局混入,让每一个vue实例都可以享用此mixin。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption);
console.log(this.myData);
}
}
})
new Vue({
myOption: 'option1!',
data: () => {
return {
myData: "data1"
}
}
})
// => "hello1!"
new Vue({
myOption: 'option2!',
data: () => {
return {
myData: "data2"
}
}
})
// => "option2!" "data2"
请你实现Mixin(手动狗头脸)
核心其实就下面两小段,只不过我们实现mergeOptions这个方法稍费些功夫。
// init.js
Vue.prototype._init = function(options) {
const vm = this;
// vm.constructor === Vue ~ vm实例的构造函数是Vue
vm.$options = mergeOptions(vm.constructor.options, options); // 后面会对options进行扩展操作
// 对数据进行初始化 watch computed props data ...
initState(vm); // vm.$options.data 数据劫持
if (vm.$options.el) {
// 将数据挂载到这个模板上
vm.$mount(vm.$options.el);
}
}
// global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue) {
// 创建一个严格空对象,可以理解为{}
Vue.options = Object.create(null);
// 用来存所有vue子类的初始构造函数大Vue
Vue.options._base = Vue;
Vue.mixin = function (mixinOptions) {
// 将属性合并到Vue.options上
// 这里的this是,谁调用它就指向谁,那么往上看两个例子,Vue.mixin(xxx),所以这里指向的是大Vue
this.options = mergeOptions(this.options, mixinOptions)
return this;
}
}
结合上面全局混入的例子,我们可以看到,在Vue构造上调用mixin方法的时候,我们给这个大Vue首先增加了一个options属性并且赋值为一个空对象。接下来我们执行了
this.options = mergeOptions(this.options, mixinOptions)
,那么也就是说,无论Vue.mixin调用了多少次,我们都会依次把mixinOptions给它合并。接下来,我们new Vue()
的时候,构造函数调了_init方法,我们把刚刚定义的全局mixinOptions又与最新的真正的vue options
进行混合,得到最后的options,最后挂载到vm.$options上。
接下来,就是核心的函数mergeOptions的实现了。
// utils/index
let lifeCycleHooks = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
]
let strats = {}; // 存放各种策略
// {} {beforeCreate:Fn} => {beforeCreate:[fn]}
// {beforeCreate:[fn]} {beforeCreate:fn} => {beforeCreate:[fn,fn]}
function mergeHook(parentVal, childVal) {
if (childVal) {
if (parentVal) {
return parentVal.concat(childVal); // 后续
} else {
return [childVal]; // 第一次
}
} else {
return parentVal
}
}
lifeCycleHooks.forEach(hook => {
strats[hook] = mergeHook
});
strats.components = function(parentVal, childVal) {
// Vue.options.components
let options = Object.create(parentVal); // 根据父对象构造一个新对象 options.__proto__= parentVal
if (childVal) {
for (let key in childVal) {
options[key] = childVal[key];
}
}
return options
}
export function mergeOptions(parent, child) {
const options = {}; // 合并后的结果
for (let key in parent) {
mergeField(key);
}
for (let key in child) {
if (parent.hasOwnProperty(key)) {
continue;
}
mergeField(key);
}
function mergeField(key) {
let parentVal = parent[key];
let childVal = child[key];
// 策略模式
if (strats[key]) { // 如果有对应的策略就调用对应的策略即可
options[key] = strats[key](parentVal, childVal)
} else {
if (isObject(parentVal) && isObject(childVal)) {
options[key] = { ...parentVal, ...childVal }
} else {
// 父亲中有,儿子中没有
options[key] = child[key] || parent[key];
}
}
}
return options
}
- 首先我们要明确的是,进入
mergeOptions
的两个参数parent和child,是以child优先级为第一的,也就是说如果有覆盖的情况,那么一定是child覆盖parent。 - 执行
mergeOptions
,要保全parent和child里面的所有key都不丢失,所以遍历了两个object,但是又不能重复,所以在第二次遍历的时候判断之前没有遍历过的才去mergeField
。 - 接下来就是
mergeField
合并策略,我们提前定义好每一种不同的key名,可能会用到的不同的合并方法,如果匹配到了那我们就用这些对应的策略。 - 如果是非策略模式,那么我们就认为这是一个普通的选项。此时,如果父子元素值都是对象的话,那么我们就以childVal为优先,合并;如果不是对象,那么还是以儿子为优先,childVal没有值才设置为parentVal。
原文链接
如果你在非掘金地址看到这篇文章,这里有原文传送门,您的点赞是对我最大的支持!
完 🎉
下一讲,组件初始化流程