Vue|mixins混入咋个用?

官方原话:混入 (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中的方法opendialog组件中也是没有的。虽然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 混入 可以使得代码结构这么清晰和有逻辑性。且,代码可复用性提高 ~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值