Interviewer: Tell me about your understanding of Vue's mixin. What are the application scenarios?
1. What is a mixin
Mixin
A class in an object-oriented programming language that provides method implementations. Other classes can access mixin
the methods of the class without having to be a subclass of it
Mixin
Classes are usually used as functional modules, and "mixed in" when the function is needed, which is conducive to code reuse and avoids the complexity of multiple inheritance
Mixins in Vue
First look at the official definition
mixin
(mixins), which provide a very flexible way to distributeVue
reusable functionality in components.
The essence is actually an js
object, which can contain any function options in our components, such as data
, components
, methods
, created
, computed
etc.
We only need to pass the common function mixins
into the options as an object, and when the component uses mixins
the object, all mixins
the options of the object will be mixed into the options of the component itself.
In Vue
we can mix in locally and globally
Partial mix-in
Define an mixin
object with options
components data
and methods
properties
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
mixins
Components call mixin
objects through properties
Vue.component('componentA',{
mixins: [myMixin]
})
When this component is in use, it mixes mixin
the methods inside, automatically executes created
the life hook, and executes hello
the method
global mixins
By Vue.mixin()
making a global mixin
Vue.mixin({
created: function () {
console.log("全局混入")
}
})
Special care is required when using global mixins, as it affects every component instance (including third-party components)
PS: Global mix-ins are often used for plug-in writing
Precautions:
When the component has mixin
the same options as the object, the option of the component will be overwritten when the recursive merge is mixin
performed
But if the same option is a life cycle hook, it will be merged into an array, the hook will be executed first mixin
, and then the hook of the component will be executed
2. Usage scenarios
In daily development, we often encounter the same or similar code that needs to be used in different components, and the functions of these codes are relatively independent
Vue
At this time, mixin
the same or similar code can be proposed through the function
for example
Define a modal
pop-up window component, which is used isShowing
to control the display internally
const Modal = {
template: '#modal',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
Define a tooltip
prompt box, which is internally isShowing
controlled by
const Tooltip = {
template: '#tooltip',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
By observing the above two components, it is found that the logic of the two is the same, and the code control display is also the same, which mixin
will come in handy at this time
First extract the common code, write amixin
const toggle = {
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
In use of the two components, only need to introducemixin
const Modal = {
template: '#modal',
mixins: [toggle]
};
const Tooltip = {
template: '#tooltip',
mixins: [toggle]
}
Through the above small example, let us know that Mixin
it is so interesting, convenient and practical to encapsulate some reusable functions
3. Source code analysis
start Vue.mixin
with
Source location: /src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
Mainly call merOptions
the method
Source location: /src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (child.mixins) {
// 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {
}
let key
for (key in parent) {
mergeField(key) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
}
for (key in child) {
if (!hasOwn(parent, key)) {
// 如果parent已经处理过某个key 就不处理了
mergeField(key) // 处理child中的key 也就parent中没有处理过的key
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
}
return options
}
From the source code above, we get the following points:
- Priority recursive processing
mixins
- First traverse the merge
parent
,key
callmergeField
the method to merge, and then save it in the variableoptions
- Traversing again
child
, merging to make up whatparent
is not inkey
, callmergeField
the method to merge, and save it in the variableoptions
mergeField
merged by the function
The following are Vue
several types of merge strategies for
- Replacement
- Combined
- queue type
- Overlay type
Replacement
The replacement type incorporates props
, methods
, inject
,computed
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
const ret = Object.create(null) // 创建一个第三方对象 ret
extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
return ret
}
strats.provide = mergeDataOrFn
The ones with the same name props
, methods
, inject
, computed
will be replaced by the later ones
Combined
And the merge type has:data
strats.data = function(parentVal, childVal, vm) {
return mergeDataOrFn(
parentVal, childVal, vm
)
};
function mergeDataOrFn(parentVal, childVal, vm) {
return function mergedInstanceDataFn() {
var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
var parentData = parentVal.call(vm, vm)
if (childData) {
return mergeData(childData, parentData) // 将2个对象进行合并
} else {
return parentData // 如果没有childData 直接返回parentData
}
}
}
function mergeData(to, from) {
if (!from) return to
var key, toVal, fromVal;
var keys = Object.keys(from);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
toVal = to[key];
fromVal = from[key];
// 如果不存在这个属性,就重新设置
if (!to.hasOwnProperty(key)) {
set(to, key, fromVal);
}
// 存在相同属性,合并对象
else if (typeof toVal =="object" && typeof fromVal =="object") {
mergeData(toVal, fromVal);
}
}
return to
}
mergeData
The function traverses all attributes of the data to be merged, and then merges according to different situations:
- When the target data object does not contain the current attribute, call
set
the method to merge (the set method is actually some method of merging and reassigning) - When the target data object contains the current attribute and the current value is a pure object, recursively merge the current object value, this is done to prevent the object from having new attributes
queue
Queued merges are: all lifetimes andwatch
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
// 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];
}
ret[key$1] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child];
}
return ret
};
Life cycle hooks and watch
are merged into an array, and then traversed in a positive order to execute
Overlay type
Overlapping merges are: component
, directives
,filters
strats.components=
strats.directives=
strats.filters = function mergeAssets(
parentVal, childVal, vm, key
) {
var res = Object.create(parentVal || null);
if (childVal) {
for (var key in childVal) {
res[key] = childVal[key];
}
}
return res
}
The superposition type is mainly through the layer-by-layer superposition of the prototype chain
summary:
- Replacement strategies include
props
,methods
,inject
,computed
, which are to replace the old parameters with new parameters with the same name - The merge strategy is to merge and reassign
data
by methodset
- The queue-type strategy has life cycle functions and
watch
the principle is to store the functions in an array, and then traverse them in order and execute them sequentially - Superposition types include
component
,directives
,filters
, and are superimposed layer by layer through the prototype chain
references
- https://zhuanlan.zhihu.com/p/31018570
- https://juejin.cn/post/6844904015495446536#heading-1
- https://juejin.cn/post/6844903846775357453