官方原话:混入 (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 等。如果混入对象中这些字段对应的对象中有相同的字段或同名方法。则会舍弃执行混入对象的这些内容,执行原组件的。从上面执行日志和总结来看。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 混入 可以使得代码结构这么清晰和有逻辑性。且,代码可复用性提高 ~
