Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue的mixins混入 #96

Open
andyChenAn opened this issue Dec 17, 2020 · 0 comments
Open

Vue的mixins混入 #96

andyChenAn opened this issue Dec 17, 2020 · 0 comments

Comments

@andyChenAn
Copy link
Owner

Vue的mixins混入

混入 (mixin) 提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

全局混入

当我们调用Vue.mixin()方法的时候,我们可以把需要的东西全局混入到每一个Vue实例上。我们来看下具体代码:

Vue.mixin = function (mixin) {
    // 这里的this指的是Vue。
    // this.options指的是Vue.options
    this.options = mergeOptions(this.options, mixin);
    return this
};

var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
];

Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
    Vue.options[type + 's'] = Object.create(null);
});

Vue.options._base = Vue;

上面代码,主要做了以下几件事情:

  • 1、我们可以创建全局组件,全局指令,全局过滤器
  • 2、我们可以调用Vue.mixin方法进行全局混入
  • 3、全局混入也就是把需要的东西混入到全局的options中(Vue.options)

全局混入就是调用Vue.mixin()方法来实现,当我们全局注册一个混入,会影响注册之后所有创建的每一个Vue实例。因为混入的数据,都会被合并到Vue.options上。

mergeOptions方法:

mergeOptions方法的作用是将两个options合并成一个新的options。

function mergeOptions (parent,child,vm) {
    // ... 省略代码
    if (!child._base) {
        if (child.extends) {
            parent = mergeOptions(parent, child.extends, vm);
        }
        // 如果存在mixins字段,那么我们就可以mixins中的数据全部合并到parent上
        if (child.mixins) {
            for (var i = 0, l = child.mixins.length; i < l; i++) {
                parent = mergeOptions(parent, child.mixins[i], vm);
            }
        }
    }
    
    var options = {};
    var key;
    // 先处理parent上的key
    // 调用mergeField方法,就是为了获取处理key对应的合并策略
    for (key in parent) {
        mergeField(key);
    }
    // 遍历child上的key,排除掉已经在parent上处理过的key
    // 留下的只是parent上没有处理过的key,然后获取这些key对应的合并策略
    // 因为parent上已经有了child上存在的key的合并策略,所以就不需要再处理了
    for (key in child) {
        if (!hasOwn(parent, key)) {
            mergeField(key);
        }
    }
    // 拿到相应key的合并策略函数,进行合并字段
    function mergeField (key) {
        var strat = strats[key] || defaultStrat;
        options[key] = strat(parent[key], child[key], vm, key);
    }
    // 最终返回合并后的对象
    return options
}

mergeOptions方法主要做了以下几件事情:

  • 1、遍历mixins,合并每一个mixin
  • 2、首先处理全局的options上的key,并且获取key上对应的合并策略方式,进行合并
  • 3、再处理每一个mixin上的key,并且获取key上对应的合并策略方式,排除掉已经在全局options处理的key(当全局options和mixin存在相同key时)
  • 4、返回已经合并后的对象

strats其实就是一个对象,里面保存了很多种基于不同属性的合并策略:

strats = {
    activated : function mergeHook () {},
    beforeCreate : function mergeHook () {},
    beforeDestroy : function mergeHook () {},
    beforeMount : function mergeHook () {},
    beforeUpdate : function mergeHook () {},
    components : function mergeAssets () {},
    computed : function () {},
    created : function mergeHook () {},
    data : function () {},
    deactivated : function mergeHook () {},
    destroyed : function mergeHook () {},
    directives : function mergeAssets () {},
    el : function () {},
    errorCaptured : function mergeHook () {},
    filters : function mergeAssets () {},
    inject : function () {},
    methods : function () {},
    mounted : function mergeHook () {},
    props : function () {},
    propsData : function () {},
    provider : function mergeDataOrFn () {},
    serverPrefetch : function mergeHook () {},
    updated : function mergeHook () {},
    watch : function () {}
}

如果需要合并的属性策略不存在,那么就使用默认的合并策略:

var defaultStrat = function (parentVal, childVal) {
    return childVal === undefined ? parentVal : childVal;
};

默认的合并策略方式就是:优先使用组件options的,如果没有那么就使用组件mixins中的options的,如果还是没有,那么就使用全局options的。

处理data混入

Vue在处理data混入的时候,如果混入的data与组件本身的data存在同名的属性,那么Vue会优先使用组件本身的属性,而不会使用混入的属性。

function mergeData (to, from) {
    if (!from) { return to }
    var key, toVal, fromVal;
    
    var keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from);
    
    for (var i = 0; i < keys.length; i++) {
        key = keys[i];
        if (key === '__ob__') { continue }
        toVal = to[key];
        fromVal = from[key];
        // 如果不存在这个属性,那么就会重新设置属性
        // 如果存在同名的属性,那么就会忽略混入进来的属性,而使用组件data的属性
        if (!hasOwn(to, key)) {
            set(to, key, fromVal);
        } else if (
            // 如果存在这个属性,并且属性的值都是对象
            // 那么就递归调用mergeData函数,继续合并对象属性
            toVal !== fromVal &&
            isPlainObject(toVal) &&
            isPlainObject(fromVal)
        ) {
            mergeData(toVal, fromVal);
        }
    }
    // 最终返回合并后的对象
    return to
}

通过上面的代码中,我们可以看到,如果混入的data的属性与组件的data的属性同名,那么会使用组件的data的属性。

处理生命周期钩子的混入

Vue在处理生命周期钩子混入的时候,会将混入的生命周期钩子和组件的生命周期钩子合并成一个数组,混入的生命周期钩子先调用,组件的生命周期钩子后调用。

function mergeHook (parentVal,childVal) {
    // 如果组件与mixin存在相同的生命周期钩子,那么会调用合并钩子
    var res = childVal
    ? parentVal
    ? parentVal.concat(childVal)
    : Array.isArray(childVal)
    ? childVal
    : [childVal]
    : parentVal;
    return res
    ? dedupeHooks(res)
    : res
}

// 删除重复
function dedupeHooks (hooks) {
    var res = [];
    for (var i = 0; i < hooks.length; i++) {
        if (res.indexOf(hooks[i]) === -1) {
            res.push(hooks[i]);
        }
    }
    return res
}

// 组件的生命周期钩子在合并的时候都会调用mergeHook函数,来合并钩子
LIFECYCLE_HOOKS.forEach(function (hook) {
    strats[hook] = mergeHook;
});

在混入生命周期钩子的时候,会先把生命周期钩子转为数组形式([createdFn]),如果存在钩子那么就合并钩子。

处理methods,props,computed,inject的混入

这几个混入都有一个共同点,就是都是对象,所以对于这些对象的混入,Vue的处理方式是,如果出现同名属性,那么会覆盖同名属性,就使用组件的属性

strats.props =
strats.methods =
strats.inject =
strats.computed = function (parentVal,childVal,vm,key) {
    if (childVal && "development" !== 'production') {
        assertObjectType(key, childVal, vm);
    }
    // 如果组件本身不存在,那么就直接返回混入
    if (!parentVal) { return childVal }
    
    var ret = Object.create(null);
    extend(ret, parentVal);
    if (childVal) { extend(ret, childVal); }
    return ret
};

处理watch的混入

Vue处理watch的混入和处理生命周期钩子的混入类似,会将同名的属性合并成一个数组,然后顺序调用,最后调用的就是组件本身的watch的属性

strats.watch = function (parentVal,childVal,vm,key) {
    if (parentVal === nativeWatch) { parentVal = undefined; }
    if (childVal === nativeWatch) { childVal = undefined; }
    if (!childVal) { return Object.create(parentVal || null) }
    {
    assertObjectType(key, childVal, vm);
    }
    if (!parentVal) { return childVal }
    var ret = {};
    extend(ret, parentVal);
    for (var key$1 in childVal) {
        var parent = ret[key$1];
        var child = childVal[key$1];
        if (parent && !Array.isArray(parent)) {
            parent = [parent];
        }
        ret[key$1] = parent
        ? parent.concat(child)
        : Array.isArray(child) ? child : [child];
    }
    return ret
};

处理components,directives,filters

Vue处理这三个的混入是这样的:

function mergeAssets(parentVal, childVal, vm, key) {
    // 把一个对象直接当做另外一个对象的原型
    var res = Object.create(parentVal || null);
    if (childVal) {
        assertObjectType(key, childVal, vm);
        return extend(res, childVal);
    } else {
        return res;
    }
}

上面代码中,把一个对象当做另外一个对象的原型,这样做的好处就是,能够同时访问两个相同的字段,避免被覆盖。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant