菜鸡手写vue(六)-mixin混入

111 阅读3分钟

说明

将一些相似业务的代码抽离出来,方便在其他组件使用,提高代码的复用率,提高开发效率。当某个组件需要用到这部分代码时,可以合并这部分代码到组件中。 缺点:容易造成数据来源不明确,在组件中使用了该组件中未曾声明的属性,其实该属性来自于混入的那部分代码,不利于项目维护。

使用

使用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;
}