以 Toast 为例讲解 Vue 组件的概念

4,425 阅读4分钟

组件是 Vue 的一个极其重要的概念。在移动端网页开发时,Toast 组件使用也是非常频繁的。本文便以 Toast 组件为例,来讲解 Vue 组件的部分知识点。

1. 单文件组件

日常开发时,我们项目文件夹通常都是使用 vue-cli 创建的,以 单文件组件 的方式来组织代码的。按照平日的开发流程,现在先创建一个 Toast.vue 文件。

我们开始关注实现 Toast 组件的一些要点:

  1. 该组件有两个 prop : visible 与 msg。visible 控制显示与隐藏,msg 是显示的内容
  2. 该组件的位置: 相对于屏幕视口来确定位置,position 设置为 fixed,并且把 z-index 值设置大一点。为了让元素居中显示,要用到 translate 属性
  3. 该组件显示或者隐藏时添加了动画: 使用 transition 组件来实现过渡效果

根据上文的分析,现在的代码如下。

<template>
  <transition name="toast-bounce">
    <div class="toast" v-show="visible">
      <p class="toast-msg">{{ msg }}</p>
    </div>  
  </transition>
</template>
<script>
    export default {
      name: 'Toast',
      props: {
          visible: {
              type: Boolean,
              required: true
          },
          msg: {
              type: String,
              required: true
          }
      }
    }
</script>
<style lang="scss" scoped>
// 添加过渡效果
  .toast-bounce-enter, .toast-bounce-leave {
    opacity: 0;
  }
  .toast {
    box-sizing: border-box;
    position: fixed;
    max-width: 80%;
    left: 50%;
    top: 50%;
    padding: 20px;
    z-index: 99;
    transition: all .3s ease;
    transform: translate(-50%, -50%);
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    text-align: center;
    .toast-msg {
      text-align: center;
    }
  }
</style>

在需要使用 Toast 组件的页面,引入该组件,注册之后就能使用了。

2. 如何手动创建组件

以单文件组件引入,有几个不方便的地方。

  • Toast 每次使用都要导入
  • 父组件的 state 要包含一个 visible 的属性 (假设我们没有使用mixin)
  • 设置 父组件的 visible 为 true 之后,用一个定时器再把 visible 设置为 false

类似这样使用频率较高的组件,最便捷的方式是通过调用一个函数来完成组件的创建挂载以及销毁。但而在开发项目过程中,我们把精力放在了组件的实现上,忽略了一些这些操作。接下来我们将演示手动如何创建 Toast 组件,附加到 document.body 下,在显示一段时间后,销毁组件,再移除该元素。

组件的创建与挂载

Vue 单文件的 script 标签仅仅是导出一个包含组件选项的对象。为了创建组件,先使用 Vue.extend 得到一个构造器,接下来通过 new 进行实例化。这里的构造函数有一个可选参数,类型是 Object,可以传入的属性有 el,propsData等。

import ToastConfig from './Toast.vue'
// 构造器
const ToastConstructor = Vue.extend(ToastConfig)
// 通过 new 创建组件
const instance = new ToastConstructor()

Toast 组件在创建时,两个 prop: visible, msg 必须要传入。在 new 实例化时,配置 propsData 参数来设置组件所需的 prop。

const instance = new ToastConstructor({
    propsData: {
        msg: 'msg',
        visible: false   
    }
})

组件创建与挂载是两个不同的概念,组件创建时,挂载阶段还没开始,DOM 元素中不含该节点。在 挂载 了之后,才会被浏览器渲染。
在创建实例时,不但要设置 propsData属性,还要设置挂载点 el 属性,并且将组件附在 document.body 之后。

const instance = new ToastConstructor({
    el: document.createElement('div'),
    propsData: {,
        msg: 'msg',
        visible: false   
    }
})
// 组件创建成功之后挂载
document.body.appendChild(instance.$el)

挂载之后,显示 toast 组件。再开启一个定时器,在 3s 之后隐藏组件。

Vue.nextTick(() => {
    instance.visible = true
    setTimeout(() => {
      instance.close()
    }, 3000)
})

组件的销毁

Toast 组件隐藏的动画结束时,触发 transitionend 事件。instance.$el 返回组件的根元素,监听该元素的 transitionend 事件,在回调函数中 销毁 组件,同时从DOM树中移除。

// 在 ToastConstructor 上添加两个销毁组件与移除DOM元素的函数
// 隐藏组件
ToastConstructor.prototype.close = function () {
  this.visible = false
  this.$el.addEventListener('transitionend', this.destroyeInstance.bind(this))
}
// 销毁组件,移除DOM元素
ToastConstructor.prototype.destroyeInstance = function () {
  this.$destroy(true)
  this.$el.removeEventListener('transitionend', this.destroyeInstance)
  this.$el.parentNode.removeChild(this.$el)
}

完整代码片段点击 这里

3. 小的调整

如果在短时间频繁调用 toast 函数,document.body 下面存在多个 toast 组件,后一个会覆盖前一个。考虑到移动端的特点,我们希望在同一个时间点上,不论 toast 函数调用有多频繁,document.body 只有会有一个 toast 组件。
接下来的调整思路大致如下:

  1. 检测是否存在 Toast 组件
  2. 不存在,创建即可
  3. 存在的话,重新设置 state 中的 visible 与 msg,移除销毁组件的定时器,移除对 transitionend 时间的监听

代码需要进行一些的调整。

// 在函数之外,定义两个变量
// 组件销毁时,instance 也要置为 null
let instance = null
let timer = null
function toast (msg = '默认信息') {
  // 判断 instance 是否存在
  if (instance) {
    instance.visible = true
    instance.msg = msg
    if (timer) {
      clearInterval(timer)
    }
    instance.$el.removeEventListener('transitionend', instance.destroyeInstance)
  } else {
      //... 
  }
  // ...
}

完整代码片段点击 这里

当然我们也可以再更进一步,把 toast 函数注册为 Vue 插件,在需要的地方通过 this.$toast 调用即可。element-ui 的 message,vux 的 Toast 都支持插件方式调用。

4. 总结

跟一些成熟的 Vue ui 库,例如:mint-ui 的 Toast 组件相比,现在的 Toast 组件不足之处很多。但通过编写一个极其简单的 Toast 组件,了解到 Vue 组件的部分概念,知道了到如何手动创建,销毁组件,如何结合 transition 制作一些简单的动画,有助于我们更好的理解 Vue 组件的思想 ,而且以后在使用第三方库的一些组件时,能大致知晓其原理。

参考资料