前言
在我们平时的移动端开发中, 使用最多, 频率最好的可能就是今天咱们要写这个 toast 组件了; 他可以在请求错误, 表单验证等情境下使用, 而这个组件有可能也是最容易实现的一个组件. 咱们今天一步一步的来抽象一个这样的组件, 方便平时开发中使用. Github 源码(不麻烦的话帮忙 start, 请各位大爷赏个星星) https://github.com/webKity/r-ui/tree/master/src/components/tip . http://demo.divpc.cn/#/tip .
开始制作
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 的知识点: install https://cn.vuejs.org/v2/api/#Vue-use 和 https://cn.vuejs.org/v2/api/#Vue-extend 感兴趣的朋友可以点击链接去官网看详细介绍. 这个组件的代码大家可以去我的 GITHUB 上查看.
来源: https://juejin.im/entry/5b0ba122f265da091c5f72a9