前言
近期闲来无事,就想折腾一点东西,无奈技术水平太低,做不了什么高深复杂的项目,所以还是先从简单的做起吧。平时我们做项目的时候或多或少都会用到一些组件库,比如element、muse之类的,所以这次我想自己手动写一个组件,封装成一个包,可以暴露到外部去调用。
效果预览
废话不多说,这次写的是一个Vue的消息弹窗组建,借鉴了element的message组件,大家可以去看一看element的源码,里面一些组件的实现方式很巧妙,非常值得去学习。话不多说,先来看看最终的实现效果:
代码
第一步应该是写一个模板:
<template>
<transition name="msg-fade" @after-leave="afterLeave">
<div :class="classes" class="msg-container" v-show="visible" role="alert" @mouseenter="clearTimer" @mouseleave="startTimer">
<span>{{ message }}</span>
<i class="msg-icon-wrapper" v-if="showCloseButton" @click="close">
<img class="msg-icon" src="./close.svg" alt="close" />
</i>
</div>
</transition>
</template>
可以看到,html内容是很简单的,主要是几个标签和一些props,还有几个事件,然后我们再来看js:
<script>
export default {
name: 'Message',
props: {
// 要显示的消息
message: {
type: String,
require: true
},
// 主题
type: {
type: String,
default: 'normal'
// normal success error warning
},
// 是否显示关闭按钮
showCloseButton: {
type: Boolean,
default: false
},
// 组件出现的位置
location: {
type: String,
default: 'top-center'
// top-center top-right top-left bottom-center bottom-right bottom-left
},
// 自定义样式名
className: {
type: String,
default: ''
},
// 持续时间
duration: {
type: Number,
default: 2000
}
},
data () {
return {
// 动态修改样式
classes: [
{
'msg-icon-padding': this.showCloseButton
},
`msg-${this.type}`,
`msg-${this.location}`,
this.className
],
// 控制组件显示
visible: false,
// 是否关闭
closed: false,
// 定时器
timer: null
}
},
watch: {
closed (newVal) {
if (newVal) {
this.visible = false
}
}
},
methods: {
close () {
this.closed = true
},
afterLeave () {
this.$destroy(true)
this.$el.parentNode.removeChild(this.$el)
},
clearTimer () {
clearTimeout(this.timer)
},
startTimer () {
const timeout = this.duration
if (timeout > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close()
}
}, timeout);
}
}
},
mounted () {
this.startTimer()
this.visible = true
},
beforeDestroy () {
this.clearTimer()
}
}
</script>
可以看到组件的代码逻辑也是非常简单的,props的处理我就不多细说了,可能每一个人的需求都一样。重点要讲一讲的是组件的创建和销毁过程。在组件挂载后,组件会调用startTimer方法生成一个定时器,按照duration指定的时间后调用close方法,close方法会设定data的closed为true,visible监听到closed变为true后就变为false,所以根据v-show这个指令,组件的display样式属性就会变成none。最后Vue官方内置的过渡组件transition会调用一系列钩子函数,我们直接在afterLeave这个钩子上销毁组件和清除dom。看到这里,可能有同学会有疑问了?为啥要这样销毁组件啊,直接用一个prop控制组件显示不就可以了吗?其实对于一般的组件我们是没必要弄成这样,但是我们得思考一下消息弹窗的使用场景啊。一般情况下,消息弹窗都是在完成了某种操作后,弹出来告诉用户操作成功与否,所以我们通常用js控制消息弹窗显示。因此,我们的消息弹窗组件不仅仅是声明式的,更应该是命令式的,直接用js控制显示与否。
很多同学应该都使用过element,element在全局引入的时候会向vue上挂载一个message的方法,一般我们都是this.$message
这样来操作的,我的目标也是做成这样子。
import Vue from 'vue'
import Message from './Message.vue'
const messageConstructor = Vue.extend(Message)
let count = 1
const notify = (options = {}) => {
// 如果Vue运行在服务端,放弃调用
if (Vue.prototype.$isServer) {
return
}
if (typeof options === 'string') {
options = {
message: options
}
}
// 创造实例
const instance = new messageConstructor({
propsData: options
})
// 渲染并挂载到body上
const vm = instance.$mount()
document.body.appendChild(vm.$el)
vm.$el.style.zIndex = count
// 返回一个主动关闭的方法
const close = () => {
vm.closed = true
}
return close
}
export default notify
这里我们用了Vue的一个方法,vue.extend
,这个方法接受一些配置参数,返回一个可以生产vue实例的构造函数,其实我们平时使用的组件一般都是这些构造函数实例化的对象。代码的逻辑很简单,当notify方法被调用的时候会创造一个message组件的实例对象,同时传入props,最后渲染成dom并挂载到document.body上。最后我们提供一个install方法将notify和message组件全局安装到Vue中:
class Plugin {
static install (Vue) {
Vue.prototype.notify = notify
Vue.mixin({
components: {
'oce-message': Message
}
})
}
}
export default Plugin
// 全局注册
import oce-message from 'oce-message'
import Vue from 'vue'
Vue.use(oce-message)
// 使用
<oce-message ... />
this.notify(...)
好了,一个简单的消息弹窗组件就完成了,具体的代码和使用方法在这里oce-message可以找到,欢迎star和提issue。
后续
这个组件有点简陋,其实有一些可以优化的地方:
- 样式
- 过渡动画
- 性能(实例是否可以做成一个单例?)