官方原话:
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
这里先通过自己写的vue代码对该混入[mixins]
进行功能验证,然后通过elment-ui源码对该功能的易用性进行再次吹捧 ~
本地代码案例
局部混入
在Ban-Acc-Transfer-c.vue
中的<script>
内定义要混入的对象[
要混入的内容必须是对象]
// src/views/Bank-Acc-Transfer-c.vue
<script>
const MixinsSlot = {
data(){
return {
allBalance: "99.98",
currency: "欧元",
}
},
mounted() {
console.log('通过mixins 生命周期 调用方法 mounted ')
this.currency = this.currency + '-duble-'
},
methods:{
sayHello(){
console.log('点击下一步按钮 通过mixins 在不改变原代码结构情况下 加入代码方法 sayHello')
}
}
}
要混入的对象中有,vue生命周期钩子函数data()、mounted()和属性对象method 。
接下来是原组件对象,并通过一个字段mixins
将上截图对象混入。注意,字段mixins
对应的值是数组
// src/views/Bank-Acc-Transfer-c.vue
export default {
// mixins:[MixinsSlot],
data() {
return {
allBalance: "9.98",
payAcc: "62284806221890098",
currency: "人民币",
mType: "币种",
payee: "请选择"
};
},
mounted() {
console.log('原码生命周期 调用方法 mounted ')
this.currency = this.currency + '-again'
},
methods: {
handleChange(value) {},
callbackInput(trans_acc = '0.0'){
console.log('实时打印回调-子组件动态输入的内容>>>', trans_acc)
},
sayHello(){
console.log('点击下一步按钮 原码逻辑中 调用方法 sayHello')
}
}
};
</script>
未混入时执行结果日志显示
未混入时生命周期mounted方法中执行
mounted() {
console.log('原码生命周期 调用方法 mounted ')
this.currency = this.currency + '-again'
},
结合截图显示ok的,应该就是 人民币-again
最上面第二个代码块,解开注释。即已混入时执行结果日志显示
当混入对象之后,混入的声明周期方法mounted方法
mounted() {
console.log('通过mixins 生命周期 调用方法 mounted ')
this.currency = this.currency + '-duble-'
},
上面截图上显示混入之后的内容是人民币-double-again
, 为什么?说明混入对象的生命周期方法mounted先执行,而后原组件中声明周期方法mounted再执行。然后才能有上面的内容显示。
从日志看,点击按钮只打印了原组件的方法。生命周期方法,混入和原组件的都执行了。
当然,从日志截图对比能再次证明上面的结论。但是,还有两个结论。
- 混入对象中data()方法与原组件data()方法,如果有相同字段则取用原组件中的。
- 混入对象中methods对象与与原组件methods对象,如果有相同方法字段则取用原组件中的。
全局混入
// main.js
import Vue from 'vue'
... ...
const MixinsSlot = {
data(){
return {
allBalance: "99.98",
currency: "欧元",
}
},
mounted() {
console.log('通过mixins 生命周期 调用方法 mounted ')
this.currency = this.currency + '-duble-'
},
methods:{
sayHello(){
console.log('点击下一步按钮 通过mixins 在不改变原代码结构情况下 加入代码方法 sayHello')
}
}
}
Vue.mixin(MixinsSlot)
new Vue({
// mixins: [MixinsSlot], // 或者使用这种方式,也能达到全局混入
router,
store,
render: h => h(App)
}).$mount('#app')
总结
- 组件中生命周期钩子方法,如created()、mounted() 等。如果与混入对象的生命周期钩子方法相同,则两者会以一种合并的方式进行执行。即,先执行混入对象的声明周期方法,再执行原组件的声明周期方法。
- 组件中非生命周期钩子方法,如data()、methods、computed和components 等。如果混入对象中这些字段对应的对象中有相同的字段或同名方法。则会舍弃执行混入对象的这些内容,执行原组件的。
element-ui 代码案例
从上面执行日志和总结来看。mixins
混入方式,可大大的提高我们的代码复用,以及改善我们的代码结构。接下来,分析以下开源组件库element-ui怎么使混入以一种灵活的方式体现可复用功能的 ~
混入
怎么起到可复用效果的?以import Popup from 'element-ui/src/utils/popup'
来讲 ~ 混入
以... ...
简略贴下源码,以其中几个属性props字段,及methods中某个方法作下介绍
// import Popup from 'element-ui/src/utils/popup
import Vue from 'vue';
... ...
export default {
props: {
visible: {
type: Boolean,
default: false
},
... ...
... ...
},
... ...
data() {
return {
... ...
};
},
... ...
methods: {
open(options) {
if (!this.rendered) {
this.rendered = true;
}
const props = merge({}, this.$props || this, options);
if (this._closeTimer) {
clearTimeout(this._closeTimer);
this._closeTimer = null;
}
clearTimeout(this._openTimer);
const openDelay = Number(props.openDelay);
if (openDelay > 0) {
this._openTimer = setTimeout(() => {
this._openTimer = null;
this.doOpen(props);
}, openDelay);
} else {
this.doOpen(props);
}
},
... ...
}
};
export {
PopupManager
};
该对象通过mixins
字段,被混入到了dialog
组件中的component.vue中。其中props中声明的visible,在dialog组件中是没有定义的。且methods中的方法open在dialog组件中也是没有的。虽然dialog组件中没有这些,但是dialog组件却在正常使用。看源码证明 ~
// element-ui/packages/dialog/src/component.vue
<template>
<transition
name="dialog-fade"
@after-enter="afterEnter"
@after-leave="afterLeave">
<div
v-show="visible"
class="el-dialog__wrapper"
@click.self="handleWrapperClick">
<div
role="dialog"
:key="key"
aria-modal="true"
:aria-label="title || 'dialog'"
:class="['el-dialog', { 'is-fullscreen': fullscreen, 'el-dialog--center': center }, customClass]"
ref="dialog"
:style="style">
<div class="el-dialog__header">
<slot name="title">
<span class="el-dialog__title">{{ title }}</span>
</slot>
<button
type="button"
class="el-dialog__headerbtn"
aria-label="Close"
v-if="showClose"
@click="handleClose">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" v-if="rendered"><slot></slot></div>
<div class="el-dialog__footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</transition>
</template>
<script>
import Popup from 'element-ui/src/utils/popup';
import Migrating from 'element-ui/src/mixins/migrating';
import emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElDialog',
mixins: [Popup, emitter, Migrating],
props: {
title: {
type: String,
default: ''
},
modal: {
type: Boolean,
default: true
},
modalAppendToBody: {
type: Boolean,
default: true
},
appendToBody: {
type: Boolean,
default: false
},
lockScroll: {
type: Boolean,
default: true
},
closeOnClickModal: {
type: Boolean,
default: true
},
closeOnPressEscape: {
type: Boolean,
default: true
},
showClose: {
type: Boolean,
default: true
},
width: String,
fullscreen: Boolean,
customClass: {
type: String,
default: ''
},
top: {
type: String,
default: '15vh'
},
beforeClose: Function,
center: {
type: Boolean,
default: false
},
destroyOnClose: Boolean
},
data() {
return {
closed: false,
key: 0
};
},
watch: {
visible(val) {
if (val) {
this.closed = false;
this.$emit('open');
this.$el.addEventListener('scroll', this.updatePopper);
this.$nextTick(() => {
this.$refs.dialog.scrollTop = 0;
});
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
} else {
this.$el.removeEventListener('scroll', this.updatePopper);
if (!this.closed) this.$emit('close');
if (this.destroyOnClose) {
this.$nextTick(() => {
this.key++;
});
}
}
}
},
computed: {
style() {
let style = {};
if (!this.fullscreen) {
style.marginTop = this.top;
if (this.width) {
style.width = this.width;
}
}
return style;
}
},
methods: {
getMigratingConfig() {
return {
props: {
'size': 'size is removed.'
}
};
},
handleWrapperClick() {
if (!this.closeOnClickModal) return;
this.handleClose();
},
handleClose() {
if (typeof this.beforeClose === 'function') {
this.beforeClose(this.hide);
} else {
this.hide();
}
},
hide(cancel) {
if (cancel !== false) {
this.$emit('update:visible', false);
this.$emit('close');
this.closed = true;
}
},
updatePopper() {
this.broadcast('ElSelectDropdown', 'updatePopper');
this.broadcast('ElDropdownMenu', 'updatePopper');
},
afterEnter() {
this.$emit('opened');
},
afterLeave() {
this.$emit('closed');
}
},
mounted() {
if (this.visible) {
this.rendered = true;
this.open();
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
}
},
destroyed() {
// if appendToBody is true, remove DOM node after destroy
if (this.appendToBody && this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
}
};
</script>
从该组件element-ui/packages/dialog/src/component.vue
中看,可以发现<template>模板
中有使用props属性visible
即该代码v-show="visible"动态绑定以控制标签的显示和隐藏。 却没有看到有任何的声明和定义。再看该dialog组件源码中第7行,以及第121行的代码使用。还有第198行代码,生命周期方法mounted中的使用。逻辑是,如果当该dialog可见this.visible==true,就执行this.open(); 然后植入到this.$el节点。但是dialog组件源码中任你找,没有方法open的定义。但是这里却可以通过this.open(); 任意使用 !!原因就是 已经 通过第48行 混入方式
合入到了当前dialog组件代码中 。
可见,mixins 混入 可以使得代码结构这么清晰和有逻辑性。且,代码可复用性提高 ~