说明
将一些相似业务的代码抽离出来,方便在其他组件使用,提高代码的复用率,提高开发效率。当某个组件需要用到这部分代码时,可以合并这部分代码到组件中。 缺点:容易造成数据来源不明确,在组件中使用了该组件中未曾声明的属性,其实该属性来自于混入的那部分代码,不利于项目维护。
使用
使用vue的静态方法mixin,vue会将mixin与组件中属性进行合并,组件中的属性会覆盖mixin中的属性。
Vue.mixin({
beforeCreate(){
console.log('beforeCreate1')
},
created(){
console.log('created1')
},
})
实现过程
合并生命周期钩子函数
给Vue声明一个静态方法mixin,并可接受一个配置对象作为参数。
export function initGlobalAPI(Vue){
Vue.options = {}; //用来存储全局的配置
Vue.mixin = function(mixin){
// 这里是已经将mixin的内容合并到Vue.options了
Vue.options = mergeOptions(Vue.options, mixin);
return this;
}
}
mergeOptions()是一个合并策略,主要在这里面完成合并操作。合并的时候分为普通合并和特殊合并,特殊合并是指命中实现定义好的属性(主要是钩子函数、data等),会采用一种策略模式匹配特殊属性。对于钩子函数的合并,其实是将钩子函数加入到一个数组里面,mixin混入的钩子函数其实会在数组前面,而组件的钩子函数会在后面,最后执行钩子函数时会将数组内钩子函数依次执行。
const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed'
]
// 合并钩子函数到一个数组里面存储
function mergeHook(parentVal, childVal){
if(childVal){
if(parentVal){ // 父子都有
return parentVal.concat(childVal);
}else{ // 父无子有
return [childVal];
}
}else{
return parentVal;
}
}
const strats = {};
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook;
})
export function mergeOptions(parent, child){
const options = {};
// 1、如果父亲有的,儿子也有,应该用儿子的
// 2、如果父亲有的,儿子没有,应该用父亲的
for(let key in parent){
mergeField(key);
}
for(let key in child){
if(!options.hasOwnProperty(key)){
mergeField(key);
}
}
function mergeField(key){
// 策略模式
if(strats[key]){
return options[key] = strats[key](parent[key], child[key])
}
if(utils.isObject(parent[key]) && utils.isObject(child[key])){
options[key] = {...parent[key], ...child[key]};
}else{
if(child[key]){
options[key] = child[key];
}else{
options[key] = parent[key];
}
}
}
return options;
}
组件合并mixin混入的内容,并执行钩子函数。执行钩子函数的时候会将钩子函数的this指向vue实例对象,所以钩子函数不能是箭头函数的写法。
Vue.prototype._init = function(options){
const vm = this;
// 将mixin的内容合并到了组件的option中
vm.$options = mergeOptions(vm.constructor.options, options);
// 执行beforeCreate钩子
callHook(vm, 'beforeCreate');
// 初始化状态
initState(vm);
// 执行created钩子
callHook(vm, 'created');
if(vm.$options.el){
vm.$mount(vm.$options.el);
}
}
export function callHook(vm, hook){ // 发布模式
const handlers = vm.$options[hook];
if(handlers){
// 将钩子函数的this指向vm实例,并执行钩子函数,所以钩子函数不能是箭头函数
handlers.forEach(hook => hook.call(vm));
}
}
合并组件合并
使用Vue.component()声明一个全局组件,实际上内部是使用了Vue.extend(),而extend()是通过一个对象创建了一个子类组件构造器函数,并返回了这个构造器。Vue.component的基本用法如下:
Vue.component('my-button', {
template: `
<div>
<button>按钮</button>
</div>
`
})
const vm = new Vue({
el: '#app',
components: {
'my-button': {
template: `
<button>内部按钮</button>
`
}
}
})
声明component静态方法,利用extend方法获取组件构造器,并将组件构造器存放在Vue.options.components中,后续实例化Vue对象时会对Vue.options.components进行合并。
Vue.options._base = Vue; // 用来保证子组件可以访问到Vue构造函数
Vue.options.components = {}; // 用来存放组件的定义
Vue.component = function(id, definition){
definition.name = definition.name || id;
// 使用this.options._base调用extend是为了保证extend中的this始终指向Vue
definition = this.options._base.extend(definition);
this.options.components[id] = definition;
}
Vue.extend = function(options){
const Super = this; // 永远指向Vue,保证Sub永远都是继承于Vue
const Sub = function VueComponent(options){
this._init(options);
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.component = Super.component;
// ...
Sub.options = mergeOptions(Super.options, options);
return Sub;
}
组件的合并策略,子与父通过原型链关联起来,这样就会优先访问子的组件。
strats.components = function(parentVal, childVal){
const res = Object.create(parentVal);
if(childVal){
for(let key in childVal){
res[key] = childVal[key];
}
}
return res;
}