前言
在我们平时的移动端开发中,使用最多,频率最好的可能就是今天咱们要写这个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