干货--手把手撸vue移动UI框架: 提示弹窗(toast)

2,364 阅读3分钟
原文链接: www.divpc.cn

前言

在我们平时的移动端开发中,使用最多,频率最好的可能就是今天咱们要写这个toast组件了;他可以在请求错误、表单验证等情境下使用,而这个组件有可能也是最容易实现的一个组件。咱们今天一步一步的来抽象一个这样的组件,方便平时开发中使用。Github源码(不麻烦的话帮忙start,请各位大爷赏个星星)demo

开始制作

DOM结构

咱们的DOM结构其实很简单,一个容器 + 提示文字 + 加一个控制显示隐藏的变量,结构如下:

<div class="r-tip" v-if="visible">
  <span class="r-tip-text" v-html="message"></span>
</div>

这里咱们用v-html来显示咱们的内容,这是考虑到有的时候,我们可能需要根据不同的使用场景增加一些ICON。最后给我们的组件添加一个淡入淡出的动画。
tip.vue/template

<template>
  <transition name="fade">
    <div class="r-tip" v-if="visible">
      <span class="r-tip-text" v-html="message"></span>
    </div>
  </transition>
</template>

CSS样式

接下来咱们给咱们的DOM结构添加一下样式,让它每次显示在屏幕中间:
tip.vue/style

.r-tip {
  position: fixed;
  max-width: 80%;
  padding: 20px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  box-sizing: border-box;
  text-align: center;
  z-index: 9999;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.r-tip-text{
  font-size: 14px;
  display: block;
  text-align: center;
}
.fade-enter-active, .fade-leave-active {
  transition: opacity .3s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}

javascript

tip.vue/script

export default {
    props: {
      message: String
    },
    data () {
      return {
        visible: false
      }
    }
  }

一个参数message,用于接受你想要显示的文本,以及一个用于控制容器显示隐藏的变量visible。
到现在为止,我们的弹窗组件基本可以使用了,但是使用起来却很麻烦,我们使用的时候只能如下使用:

<template>
  <div class="tip">
    <r-tip ref="tip" :message="msg"></r-tip>
  </div>
</template>

<script>
import rTip from './tip.vue'

export default {
  components: {rTip},
  data () {
    return {
      msg: '你好'
    }
  },
  mounted () {
    this.$nextTick(() => {
      this.$refs.tip.visiable = true
      setTimeout(() => {
        this.$refs.tip.visiable = false
      }, 2000)
    })
  }
}
</script>

<style lang="scss">
</style>

这个组件使用如此频繁,如果使用起来这么麻烦,会让人抓狂的。我希望,我在每次使用的使用只需要调用一下this.$tip(msg)就能完成上述的步骤的话就好了。那么我们应该怎么优化呢?我们可以使用vue的install方法把组件封装成一个插件!

插件封装

根据官方文档:

如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。 首先我们创造一个类Tip,并给这个类提供一个install方法,用来全局安装我们的插件,插件安装后会给VUE实例添加一个$tip方法这个方法指向咱们的Tip。这样当你调用$tip的时候就会执行Tip函数。

import Vue from 'vue'

let Tip = (options = {}) => {})
Tip.install = function () {
  Vue.prototype.$tip = Tip
}

接下来在Tip函数中实现咱们前面的使用逻辑:传入msg,显示tip两秒后隐藏tip:
tip.js

import Vue from 'vue'

let Tip = (options = {}) => {
  let duration = options.duration || 2000

  let instance = getAnInstance()
  clearTimeout(instance.timer)
  instance.message = typeof options === 'string' ? options : options.message

  document.body.appendChild(instance.$el)
  Vue.nextTick(function () {
    instance.visible = true
    instance.timer = setTimeout(function () {
      instance.close()
    }, duration)
  })
  return instance
})
Tip.install = function () {
  Vue.prototype.$tip = Tip
}

在这里咱们有一个getAnInstance方法,这个是用来得到一个Tip实例(这个实例就是之前咱们写的tip.vue)的方法;获取到这个实例后咱们把这个实例添加到BODY,然后添加到DOM成功后显示提示弹窗,并设置一个计时器,一定时间后隐藏该弹窗。那么这个getAnInstance是怎么实现的呢?
tip.js

import element from './tip.vue'
const TipConstructor = Vue.extend(element)
let tipPool = []

TipConstructor.prototype.close = function () {
  this.visible = false
  returnAnInstance(this)
}
let getAnInstance = () => {
  if (tipPool.length > 0) {
    let instance = tipPool[0]
    tipPool.splice(0, 1)
    return instance
  }
  return new TipConstructor({
    el: document.createElement('div')
  })
}

let returnAnInstance = instance => {
  if (instance) {
    tipPool.push(instance)
  }
}

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

在这一步,咱们先把咱们之前的tip.vue引入过来,使用vue.extend方法生成一个tip的构造函数,并创建一个数组,用来放置咱们创建的实例,这样方便咱们可以复用之前创建过的实例,优化性能。首先给咱们的构造器添加一个实例方法close用来关闭弹窗;在getAnInstance函数中先判断实例数组tipPool中有没有可用的实例,如果有的话就使用第一个实例,如果没有责使用构造函数创建一个实例,并返回;returnAnInstance函数用来把使用过的实例重新放回咱们的数组中方便下次继续复用;这样咱们tip.js最终代码:

import Vue from 'vue'
import element from './tip.vue'

const TipConstructor = Vue.extend(element)
let tipPool = []

TipConstructor.prototype.close = function () {
  this.visible = false
  returnAnInstance(this)
}
let getAnInstance = () => {
  if (tipPool.length > 0) {
    let instance = tipPool[0]
    tipPool.splice(0, 1)
    return instance
  }
  return new TipConstructor({
    el: document.createElement('div')
  })
}
let returnAnInstance = instance => {
  if (instance) {
    tipPool.push(instance)
  }
}

let Tip = (options = {}) => {
  let duration = options.duration || 2000

  let instance = getAnInstance()
  clearTimeout(instance.timer)
  instance.message = typeof options === 'string' ? options : options.message

  document.body.appendChild(instance.$el)
  Vue.nextTick(function () {
    instance.visible = true
    instance.timer = setTimeout(function () {
      instance.close()
    }, duration)
  })
  return instance
}
Tip.install = function () {
  Vue.prototype.$tip = Tip
}

export default Tip

最后

咱们最后梳理下咱们写这个组件中用到的两个VUE的知识点:installextend感兴趣的朋友可以点击链接去官网看详细介绍。这个组件的代码大家可以去我的GITHUB上查看。