Vue 提供了mixins 和 extends 来在组件里直接合并一些特定的属性。
用法如下:
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
var CompA = { ... }
var CompB = {
extends: CompA,
...
}
以上两者的用法来自vue官方案例,最大的区别就是extends传入的是一个组件属性对象,mixins传入的是n个组件属性对象组成的数组。
这里我们会有一个疑问,当我们注入想动的组件属性到当前组件,合并的机制是什么?是组件覆盖mixins或者extends 还是后者覆盖前者,还是两者共存。这是本文说明的重点。我们看代码:
mergeField是合并的核心代码,他会根据属性名,在合并方法合集strats里去找一个特定属性key的合并方法。调用合并方法进行合并操作。然后把合并的结果返回给vm实例。
// vue实例在初始化的时候会调用该合并方法
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
function mergeOptions (
parent,
child,
vm
) {
{
checkComponents(child);
}
if (typeof child === 'function') {
child = child.options;
}
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
if (!child._base) {
// 初始先合并extends和mixins的属性到 _base 对象上
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
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;
// 先处理_base 和mixins 或者extends 混合后的内容
for (key in parent) {
mergeField(key);
}
// 再将组件上原来的属性内容和上面的进行混合
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
// 通过属性名寻找对应的合并策略
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}
下面将展示针对不同属性的合并策略对应的源代码:
// 这个是属性里data和组件里data的合并方式
strats.data = function (
parentVal,
childVal,
vm
) {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
);
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
};
这个是属性里 props 、methods、inject、computed的合并方法
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);
// childVal 覆盖 ret里的parentVal
if (childVal) { extend(ret, childVal); }
return ret
};
这个是对el和propsData的覆盖方法
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
"option \"" + key + "\" can only be used during instance " +
'creation with the `new` keyword.'
);
}
return defaultStrat(parent, child)
};
这是对watch属性的合并方法
strats.watch = function (
parentVal,
childVal,
vm,
key
) {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) { parentVal = undefined; }
if (childVal === nativeWatch) { childVal = undefined; }
/* istanbul ignore if */
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];
}
// 按watch的key合并回掉函数到数组中,供后面顺序执行
ret[key$1] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child];
}
return ret
};
这是对provide的合并方法
strats.provide = mergeDataOrFn;
生命周期属性的覆盖方法
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
];
LIFECYCLE_HOOKS.forEach(function (hook) {
strats[hook] = mergeHook;
});
function mergeHook (
parentVal,
childVal
) {
var res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
return res
? dedupeHooks(res)
: res
}
属性中 三种vue内部类型(组件,指令,过滤器)的合并方法
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets;
});
上面的多种策略对应的最终合并函数如下:
// 合并顺序是
// to里加入只有from中特有的属性,to和from里都有的属性,保留to里的,最后返回to
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];
// in case the object is already observed...
if (key === '__ob__') { continue }
toVal = to[key];
fromVal = from[key];
if (!hasOwn(to, key)) {
set(to, key, fromVal);
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal);
}
}
return to
}
// 合并 vue中data或者reject,他们是一个函数,所以合并函数返回值
function mergeDataOrFn (
parentVal,
childVal,
vm
) {
if (!vm) {
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
// instance merge
var instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
var defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
// 默认的合并策略,优先使用childVal,childVal没有才使用parentVal
var defaultStrat = function (parentVal, childVal) {
return childVal === undefined
? parentVal
: childVal
};
// to里有的将被_from覆盖,to里没有的form有的,to里将添加。
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
总结:
属性名称 | 合并策略 | 对应合并函数 |
---|---|---|
data | mixins/extends 只会将自己有的但是组件上没有内容混合到组件上,重复定义默认使用组件上的 如果data里的值是对象,将递归内部对象继续按照该策略合并 |
mergeDataOrFn,mergeData |
provide | 同上 | mergeDataOrFn,mergeData |
props | mixins/extends 只会将自己有的但是组件上没有内容混合到组件上 | extend |
methods | 同上 | extend |
inject | 同上 | extend |
computed | 同上 | extend |
组件,过滤器,指令属性 | 同上 | extend |
el | 同上 | defaultStrat |
propsData | 同上 | defaultStrat |
watch | 合并watch监控的回掉方法 执行顺序是先mixins/extends里watch定义的回调,然后是组件的回掉 |
strats.watch |
HOOKS 生命周期钩子 | 同一种钩子的回调函数会被合并成数组 执行顺序是先mixins/extends里定义的钩子函数,然后才是组件里定义的 |
mergeHook |
mixins和extends 的合并策略都是按照上面的表格来合并的。