自定义Vue组件 - EasyToast

357 阅读7分钟

如何使用Vue自定义组件?如何使用Vue动画?接下来讲为各位看官进行解答,通过封装一个简单的吐司组件,来贯穿讲解整个组件的构建。

初始化

  1. 创建EasyToast.vue文件
<template>
  <div class="easy-toast">
    <span>我是吐司</span>
  </div>
</template>
<script>
export default {
  name: 'EasyToast',
  props: {
  },
  mounted(){
  },
  methods: {
  }
}
</script>
  1. 在需要使用Toast组件的地方进行引入
<template>
  <div id="app">
    <EasyToast />
  </div>
</template>
<script>
import EasyToast from './EasyToast';
export default {
  name: 'app',
  components:{
    EasyToast
  },
  data(){
    return {
    }
  }
}

配置

  1. 对于Toast,参数基本有三种,其中包括提示文案动画持续时间(下文讲解动画)、图标(成功、失败或者自定义图标),我们将通过Props方式传递到组件内部,首先定义组件内接收配置的属性,如下
  ...
  name: 'EasyToast',
  props: {
    // 标题
    title: {
      type: String,
      default: '我是标题'
    },
    // 动画持续时间
    duration: {
      type: Number,
      default: 3000
    },
    // 吐司模式
    mode: {
      type: String,
      default: 'normal',
      validator: function(value){
        return ['normal','error','success'].indexOf(value) > -1 
      }
    }
  }
  ...

参数尽量要配置类型及默认值,如果是特殊参数,例如枚举,Vue并没有提供响应的类型配置,我们可以使用validator进行自定义验证。

  1. 传递参数 (为节约篇幅,我们将简化代码,结合上一章节查看)
<template>
    ...
    <EasyToast :title="title" />
    ...
</template>
<script>
export default {
  name: 'app',
  ...
  data(){
    return {
       title: "提交成功"
    }
  }
}
</script>

样式

上一章节我们已经把基本参数进行了配置,目前的Toast还只是简单的一个文本显示,并且是可见的,下面我们对其进行一个样式配置。因为是组件,基于低耦合、高内聚的原则,通过scoped属性实现样式的模块化,仅作用于当前组件,防止造成全局污染。EasyToast.vue内增加样式代码如下:

<script>
...
</script>
<style lang="scss" scoped>
.easy-toast{
  width: 140px;
  background-color: #000000;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  text-align: center;
  padding: 20px 10px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  align-items: center;

  &__title{
    color: white;
    font-size: 16px;
    line-height: 22px;
  }
  &__icon{
    width: 32px;
    height: 32px;
    display: inline-block;
    margin-bottom: 12px;
    background-size: contain;
    background-position: center;

    &--error{
      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADfklEQVR4Xu2b7XEUMQyGpQqACqADoAKgAqACSAVABSQVABUQKgAqIB2QVAAlpAMx743N7OysbcmW1jeT89/zh97Hsuy1dUx3vPAd108nACcPuOMETktA6wAi8pKInqT6P5n5Wts2ul6y7T4R3VjtanqAiED0dyJ6tBJyzswX0eJq/YvIWyL6uLLtBxGdMfOtxrYqgDTA10pHl8x8phnIu46IwC4A2Cp/iei1xhuKABTi88C7Q2iIz3bBA160IGwCEBG4+x/DrO0GQSk+m444BQjF5VACcElEbwwAUDUcglF8Nh/xAHo2SwnA70XEt3AIg9ApHrZ/Yeb3VgBwmXsW5Yu6rhBEBNsbdqHnnfZcMPO5FUDPEliO4QIhif/V6Y3Znqe1QFgLggggvV5wiAlE9EG7H69nyEn8N2YubZWHIWvb4Kvkep2ed2jWjMJbnTuJv8GyaU3A6EFIA8cEYU/xVQ/IygwHohoMFYS9xasAoJKIYDlgTY/EhCqEGeLVABIEfBRdRUCYJd4EIArCTPFmAN4QUtAY3edV0d50EGqF9nRH4LEcMFS+ZGkNu/X7kPguD1jsDh4xoUd0bjMsfgiA43LogeAifhjAJAhu4l0A7AzBVbwbgJ0guIt3BRAMIUS8O4AgCGHiQwA4QwgVHwkA11ijJzzYp7ra7tlHc5vmy5C1c6ez/XLYUAiuAALEZxBhENwABIoPheACYAfxSwi4aC0+dFiX7DCAHcUvtVVfeywQhgBMEp/1uUDoBjBZvBuELgBO4nHIQXlscdmNukOeYAaQns7xVjd8k5PE4GZpGgQTAMeZ//9ik/qcBkENIEJ8dueZEFQAIsXPhtAEsIf4mRBaj6MeX3WmT1rH5YAsMaTMVUvteXx38c6eoPqAqgHozRPKOkwzv54mJ09AviAyRMxZYkgq+tRyn8rvQ+KdPaErSQo5guvUWC0PF/GOEG6Z+UHJ+FKOkGjVruq5iveCwMzFpV4C0JMmFyLeAQIyyIvH9hIA5NUhC1tbQsUPQqhuh7VdQJsruIv4Tgj9aXIYUERaEJoDaF3IUi9tkTjkPKu0U9mmOQp/JqJ3G8EOf5honrQswqx1RQRLFYmQD1dtq+mxy7pNAMkTEESQq4vT4RUz4/P1aErKWIGNOPhct5IjzQCORmmAISoPCBj3aLo8ATiaqZhkyMkDJoE/mmH/AUam2lCtvFZ1AAAAAElFTkSuQmCC');
    }
    &--success{
      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAEQElEQVR4Xu2bi3HUMBCGdyuAVEBSAUkFkAogFZBUQFIBpAJCBZAKSCogqYBLBSQVABUs83nkw/b5IUvy646d8Uwuc2fr/7SSdleWysBmZs9F5JWIHLqLz/vuKj79UUS4fovIyl33qsrnwUyHuLOZIfadiLx2omMeA4w7EblWVf5OaskAuJ5G9HlN76ZqNB5y5WAk8YxoAE74eycc9x7DEA+Iz7FDJAqAmdHjNGQs4VW4gDhX1etQ6kEAzIxJ7Isb46HPTvk75ogzVWWI9LLeAMzsVEQ+TdjrTQLxBiDc9CHQC4CZ0esAmLNdqeqFbwO9ASxEfK77q6qe+UDwAmBm30Tkrc8NZ/SdG1U96WpPJ4CF9XxVb6cntAJYuPgcBrECwVmtNQJwsz2T3jbYSdPqUAvArfM/ZrjUhXYGS+RRXZzQBOD7jIKcUNHV392p6nH1nxsAtsz1q3oJlL4W/1kC4BKbn1vk+lUADIWDYgJVBfBRRD6k8rmZ3udSVdGZ2RrADvR+rrnkBUUArJUkObtgF6pKGl/yAMY+ae4u2KOqHqwBuBoe6/4uGXHBKhsCZoY7UNZagv0RESYxvJXU/Flgo7MQOQdA71PJnbs9EKDly5jzXKpBIRBWqnqkbvb/NXflIlISn7fXzEjTSddDbA8AMTcIeWjIb2rFu+HL3gOhe4idAGDuwU+jeAcgplhzCQCKiG9C8I3wmy7xsTXKWwAwibB3F2vMzljIhFT37KHF88x7AKQIgNi3y6rFZpYiohxDPM1dAcAiu/5WVUsF08iUeizxmewUALKIqgoxEMKo4lMBKKWXRRA9IYwuPhUA0svjpr17TwiTiM8BsKH4InIeiIEwmXgReUq5DIZAmFL8ehlMGQj1gTC1eABkgVDqUNgHArHCOqurWUFiIzzfEZ2FwkMkQ60Q2lo38nZclgzxessQ6XBvCCOLpx/28oIIgcxLX7/p8T1vCBOIf1DVwzFKYp0QJhBPH5ZKYpTDhiyKNkKYSDwA/hVFXRaXIiBqGx0bECYU/6Sq2RbA2BsjQGBzkjmH9Jly1hRWuzHCaoAXpCpoTCHM55kUbvbzyvL/zdFK+rrtXlDq/dIckIPwTF99XG2O32l/QaIAIVWhdE4QOHyxMem2vSTFTL0tEyKuf+j9kpSLC4ZIkqbyiH6vyRWGwpJ2jZvghr0oWYBA4MLBiCXaer+iqfGd7wq74ZCyajQWyI39iroHewFwEJbkCZ09n8PwBuAgLGFOaB3zVS/oBaCwOuANc1siWepOBz0yU5gYSSWBkGJXOcWccO/ED39oqpI7kNIyLKbyBnqdY3Ol93/7EO09BKo3d0VVytxcY4FAOOA5IBV1gjQaQGFYkEniEYCI3Wpr6sQnJ5yjMFHCg1YBX9dyr6/lFZ/YajM7SCRniJ7v4ekmOG6I5KfIKb7mx+erXkLvVo/Pc8ghSU83te8vrv8NelTm5PcAAAAASUVORK5CYII=');
    }
  }
}
</style>

默认效果

成功

失败

显示和隐藏

目前为止,样式已经补充上了,接下来便需要进行显示和隐藏的控制。一般来讲,显示和隐藏需要由使用者来控制,而实现控制的方式有多种。

Toast采用v-model来进行控制显隐,可能并不是最优的方案,主要目的是学习一下v-model的一些特殊用法。 v-model我们通常会在input标签中使用,进行双向数据绑定,使用它的优势在与通过双向数据绑定,能够同步组件内和外部使用者的状态。

实际上,v-model等价与v-bind:value属性,所以我们在组件内部增加一个value属性(Boolean类型,默认false),来接收绑定的数据,代码如下: EasyToast.vue

  name: 'EasyToast',
  props: {
    ...
    // 吐司模式
    mode: {
      type: String,
      default: 'normal',
      validator: function(value){
        return ['normal','error','success'].indexOf(value) > -1 
      }
    },
    value: {
      type: Boolean,
      default: false
    }
  }

上一章节,我们制作好了Toast样式,但是是用户可见的, 实际上默认应该是不可见状态,我们通过增加一个修饰类class隐藏,并通过value属性来控制easy-toast--hide是否使用,代码如下: EasyToast.vue

<template>
  <div :class="['easy-toast', value === false && 'easy-toast--hide']">
    ...
  </div>
</template>
<style lang="scss" scoped>
.easy-toast{
    ...
    &--hide{
        display:none;
    }
}
</style>

调用者通过v-model来进行显示的控制,代码如下

<template>
  <div id="app">
    <EasyToast
      v-model="toastShow"
      :title="title"
    />
  </div>
</template>
<script>
export default {
  name: 'app',
  ...
  data(){
    return {
      toastShow: flase,
      title: "提交成功",
    }
  }

}

对于Toast,一般是自动隐藏的,我们就需要在显示状态且duration时间结束后使其隐藏,可以通过监听value属性来增加控制隐藏的方法,代码如下: EasyToast.vue

<script>
export default {
  name: 'EasyToast',
  props: {
    ...
    value: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    value(){
      if(this.value === true){
        // Toast显示时
        this.startTimer();
      } else {
        this.stopTimer();
      }
    }
  },
  // 组件销毁后,停止计时
  destroyed(){
    this.stopTimer();
  },
  methods: {
    // 停止计时
    stopTimer(){
      this.timer && clearTimeout(this.timer);
      this.timer = null;
    },
    // 开始计时
    startTimer(){
      this.stopTimer();
      this.timer = setTimeout(()=>{
        this.hide();
        this.stopTimer();
      }, this.duration);
    },
    // 隐藏
    hide(){
      this.$emit('input', false); // 正常Props不可以修改 通过emit “input”事件,来修改父组件状态,实现双向绑定
    }
  }
}
</script>

动画

上一章节,实现了显示和隐藏功能,但是太过单调,我们需要增加一些动画效果,使用Vue的transitions来实现,并配合css3动画,不再使用控制easy-toast--hide的方式,话不多说,直接上代码: EasyToast.vue

<template>
  <transition name="easy-toast-scale">
    <div v-show="value" class="easy-toast">
       ...
    </div>
  </transition>
</template>
<script>
...
</script>
<style lang="scss" scoped>
.easy-toast{
  ...
  transform: translate(-50%,-50%) scale(1);
  ...
}
.easy-toast-scale-enter-active, .easy-toast-scale-leave-active {
  transition: transform .3s;
}
.easy-toast-scale-enter, .easy-toast-scale-leave-to{
  transform: translate(-50%,-50%) scale(0);
}
</style>

完整代码 EasyToast.vue

<template>
  <transition name="easy-toast-scale">
    <div v-show="value" class="easy-toast">
      <i v-if="mode === 'success'" class="easy-toast__icon easy-toast__icon--success"></i>
      <i v-if="mode === 'error'" class="easy-toast__icon easy-toast__icon--error"></i>
      <span class="easy-toast__title">{{title}}</span>
    </div>
  </transition>
</template>
<script>
export default {
  name: 'EasyToast',
  props: {
    // 标题
    title: {
      type: String,
      default: '我是标题'
    },
    // 动画持续时间
    duration: {
      type: Number,
      default: 3000
    },
    // 吐司类型
    mode: {
      type: String,
      default: 'normal',
      validator: function(value){
        return ['normal','error','success'].indexOf(value) > -1 
      }
    },
    value: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    value(){
      if(this.value === true){
        // Toast显示时
        this.startTimer();
      } else {
        this.stopTimer();
      }
    }
  },
  // 组件销毁后,停止计时
  destroyed(){
    this.stopTimer();
  },
  methods: {
    // 停止计时
    stopTimer(){
      this.timer && clearTimeout(this.timer);
      this.timer = null;
    },
    // 开始计时
    startTimer(){
      this.stopTimer();
      this.timer = setTimeout(()=>{
        this.hide();
        this.stopTimer();
      }, this.duration);
    },
    // 隐藏
    hide(){
      this.$emit('input', false); // 正常Props不可以修改 通过emit “input”事件,来修改父组件状态,实现双向绑定
    }
  }
}
</script>
<style lang="scss" scoped>
.easy-toast{
  width: 140px;
  background-color: #000000;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%) scale(1);
  text-align: center;
  padding: 20px 10px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  align-items: center;

  &__title{
    color: white;
    font-size: 16px;
    line-height: 22px;
  }
  &__icon{
    width: 32px;
    height: 32px;
    display: inline-block;
    margin-bottom: 12px;
    background-size: contain;
    background-position: center;

    &--error{
      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADfklEQVR4Xu2b7XEUMQyGpQqACqADoAKgAqACSAVABSQVABUQKgAqIB2QVAAlpAMx743N7OysbcmW1jeT89/zh97Hsuy1dUx3vPAd108nACcPuOMETktA6wAi8pKInqT6P5n5Wts2ul6y7T4R3VjtanqAiED0dyJ6tBJyzswX0eJq/YvIWyL6uLLtBxGdMfOtxrYqgDTA10pHl8x8phnIu46IwC4A2Cp/iei1xhuKABTi88C7Q2iIz3bBA160IGwCEBG4+x/DrO0GQSk+m444BQjF5VACcElEbwwAUDUcglF8Nh/xAHo2SwnA70XEt3AIg9ApHrZ/Yeb3VgBwmXsW5Yu6rhBEBNsbdqHnnfZcMPO5FUDPEliO4QIhif/V6Y3Znqe1QFgLggggvV5wiAlE9EG7H69nyEn8N2YubZWHIWvb4Kvkep2ed2jWjMJbnTuJv8GyaU3A6EFIA8cEYU/xVQ/IygwHohoMFYS9xasAoJKIYDlgTY/EhCqEGeLVABIEfBRdRUCYJd4EIArCTPFmAN4QUtAY3edV0d50EGqF9nRH4LEcMFS+ZGkNu/X7kPguD1jsDh4xoUd0bjMsfgiA43LogeAifhjAJAhu4l0A7AzBVbwbgJ0guIt3BRAMIUS8O4AgCGHiQwA4QwgVHwkA11ijJzzYp7ra7tlHc5vmy5C1c6ez/XLYUAiuAALEZxBhENwABIoPheACYAfxSwi4aC0+dFiX7DCAHcUvtVVfeywQhgBMEp/1uUDoBjBZvBuELgBO4nHIQXlscdmNukOeYAaQns7xVjd8k5PE4GZpGgQTAMeZ//9ik/qcBkENIEJ8dueZEFQAIsXPhtAEsIf4mRBaj6MeX3WmT1rH5YAsMaTMVUvteXx38c6eoPqAqgHozRPKOkwzv54mJ09AviAyRMxZYkgq+tRyn8rvQ+KdPaErSQo5guvUWC0PF/GOEG6Z+UHJ+FKOkGjVruq5iveCwMzFpV4C0JMmFyLeAQIyyIvH9hIA5NUhC1tbQsUPQqhuh7VdQJsruIv4Tgj9aXIYUERaEJoDaF3IUi9tkTjkPKu0U9mmOQp/JqJ3G8EOf5honrQswqx1RQRLFYmQD1dtq+mxy7pNAMkTEESQq4vT4RUz4/P1aErKWIGNOPhct5IjzQCORmmAISoPCBj3aLo8ATiaqZhkyMkDJoE/mmH/AUam2lCtvFZ1AAAAAElFTkSuQmCC');
    }
    &--success{
      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAEQElEQVR4Xu2bi3HUMBCGdyuAVEBSAUkFkAogFZBUQFIBpAJCBZAKSCogqYBLBSQVABUs83nkw/b5IUvy646d8Uwuc2fr/7SSdleWysBmZs9F5JWIHLqLz/vuKj79UUS4fovIyl33qsrnwUyHuLOZIfadiLx2omMeA4w7EblWVf5OaskAuJ5G9HlN76ZqNB5y5WAk8YxoAE74eycc9x7DEA+Iz7FDJAqAmdHjNGQs4VW4gDhX1etQ6kEAzIxJ7Isb46HPTvk75ogzVWWI9LLeAMzsVEQ+TdjrTQLxBiDc9CHQC4CZ0esAmLNdqeqFbwO9ASxEfK77q6qe+UDwAmBm30Tkrc8NZ/SdG1U96WpPJ4CF9XxVb6cntAJYuPgcBrECwVmtNQJwsz2T3jbYSdPqUAvArfM/ZrjUhXYGS+RRXZzQBOD7jIKcUNHV392p6nH1nxsAtsz1q3oJlL4W/1kC4BKbn1vk+lUADIWDYgJVBfBRRD6k8rmZ3udSVdGZ2RrADvR+rrnkBUUArJUkObtgF6pKGl/yAMY+ae4u2KOqHqwBuBoe6/4uGXHBKhsCZoY7UNZagv0RESYxvJXU/Flgo7MQOQdA71PJnbs9EKDly5jzXKpBIRBWqnqkbvb/NXflIlISn7fXzEjTSddDbA8AMTcIeWjIb2rFu+HL3gOhe4idAGDuwU+jeAcgplhzCQCKiG9C8I3wmy7xsTXKWwAwibB3F2vMzljIhFT37KHF88x7AKQIgNi3y6rFZpYiohxDPM1dAcAiu/5WVUsF08iUeizxmewUALKIqgoxEMKo4lMBKKWXRRA9IYwuPhUA0svjpr17TwiTiM8BsKH4InIeiIEwmXgReUq5DIZAmFL8ehlMGQj1gTC1eABkgVDqUNgHArHCOqurWUFiIzzfEZ2FwkMkQ60Q2lo38nZclgzxessQ6XBvCCOLpx/28oIIgcxLX7/p8T1vCBOIf1DVwzFKYp0QJhBPH5ZKYpTDhiyKNkKYSDwA/hVFXRaXIiBqGx0bECYU/6Sq2RbA2BsjQGBzkjmH9Jly1hRWuzHCaoAXpCpoTCHM55kUbvbzyvL/zdFK+rrtXlDq/dIckIPwTF99XG2O32l/QaIAIVWhdE4QOHyxMem2vSTFTL0tEyKuf+j9kpSLC4ZIkqbyiH6vyRWGwpJ2jZvghr0oWYBA4MLBiCXaer+iqfGd7wq74ZCyajQWyI39iroHewFwEJbkCZ09n8PwBuAgLGFOaB3zVS/oBaCwOuANc1siWepOBz0yU5gYSSWBkGJXOcWccO/ED39oqpI7kNIyLKbyBnqdY3Ol93/7EO09BKo3d0VVytxcY4FAOOA5IBV1gjQaQGFYkEniEYCI3Wpr6sQnJ5yjMFHCg1YBX9dyr6/lFZ/YajM7SCRniJ7v4ekmOG6I5KfIKb7mx+erXkLvVo/Pc8ghSU83te8vrv8NelTm5PcAAAAASUVORK5CYII=');
    }
  }
}
.easy-toast-scale-enter-active, .easy-toast-scale-leave-active {
  transition: transform .3s;
}
.easy-toast-scale-enter, .easy-toast-scale-leave-to{
  transform: translate(-50%,-50%) scale(0);
}
</style>

最终效果