详解用vue编写弹出框组件
前言
最近研究了用vue编写弹出框的组件,发现其实这里面的门道还是有很多的。这篇文完全是用来记录总结下最近的学习成果,同时也希望能够帮得上正在学习纠结的你~ps:本文假设你已经了解vue2.0相关框架,因此适合有一定vue2.0基础的同学阅读。
设计组件的思考
其实单纯的编写一个弹出框组件并不难,写一个模板,然后用v-if或者v-show指令还控制组件的出现与消失。真正困扰我的是,这个组件的调用方式,这个问题纠结了我好久。
调研了下资料,有些人建议,直接把组件标签插进模板中,然后通过直接控制组件的显示隐藏来控制组件。这样写有好处,就是结构清晰,一目了然,人家一看你的代码就知道你这个页面可能会有弹出框,并且编写的组件就更容易,只需关注内部方法就好了,也不存在事件调用的困扰,维护起来也特别容易。但是缺点也很明显,如果有多个弹窗,并且不知道会有几个弹窗的情况下,感觉就不太好做,并且这种提前写模板的形式,难免会在不弹窗的时候要下载一些js文件,有可能会造成性能浪费。
也有些人建议,在写好的弹出框组件之外再做一层封装,通过动态调用的方式来控制弹出框的显示与隐藏。这样写的好处是不用事先在模板里面写好该组件的标签,只需要在想调用的地方调用下该组件,就实现了按需使用的目的,符合之前传统前端框架的编码习惯。缺点就是感觉代码写起来比较复杂,层层嵌套,并且感觉这个与MVVM模式的状态驱动界面的思想相违背。
于是我天秤座的纠结病犯了,在选择哪种技术方案的问题上,思考了很久。但是网上搜了很多,发现还是后一种实现方法用的人比较多。后来我又研究了了elementUI和iView的弹出框组件,他们也是沿用的后一种方法,想了一下后一种方法虽然代码易读性不强,但是它真正模拟了浏览器默认的alert事件,在用户需要的地方来调用,一方面节省了代码量,另一方面也很容易解决多个弹窗的情况。最后还是决定用这种模式写一个简单的弹出框组件。主要是体会这其中的机理。废话不多说,来上干货了。有啥不对的地方还请大家多多指教。(ps:对于天秤座的我,虽然选择了后一种方法,但是内心还是钟爱第一种方法,并且后一种方法并没有足够的理由说服我呀,不知道哪位有识之士能够帮忙点醒一下我,晚辈感激不尽)。
alert组件设计
单独的设计alert弹出框的逻辑是很简单的,我就直接上代码了:
<template> <transition name='fade'> <div class="alert" v-if="showAlert"> <div class="wrap"> <div class="head">{{title}}</div> <div class="body"> <slot> <p>{{message}}</p> </slot> </div> <div class="foot"> <div v-if="type === 'confirm'"> <button class="btn-base" @click="sure">确定</button> <button class="btn-gray" @click="cancel">取消</button> </div> <div v-else-if="type === 'inform'"> <button class="btn-base" @click="cancel">知道了</button> </div> </div> </div> </div> </transition> </template> <script> export default { name: 'alert', data() { return { showAlert: false, }; }, props: { title: { type: String, default: '提示', }, message: { type: String, }, type: { // 可以有confirm, 和inform两个类型 type: String, default: 'confirm', validator(value) { return value === 'confirm' || value === 'inform'; }, }, sureBtn: { type: Function, }, cancelBtn: { type: Function, }, context: { type: Object, }, }, methods: { cancel() { if (this.cancelBtn) { this.cancelBtn.apply(this.context); } this.close(); }, sure() { if (this.sureBtn) { this.sureBtn.apply(this.context); } this.close(); }, show() { this.showAlert = true; }, close() { this.showAlert = false; }, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang='scss'> .alert { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0, 0.8); z-index: 1000; transition: all .3s ease-in-out; } .wrap { position: absolute; z-index: 1002; min-width: 400px; background: #fff; left: 50%; top: 50%; transform: translate(-50%, -50%); border-radius: 4px; } .head { height: 40px; line-height: 40px; border-bottom: 1px solid #dedede; padding-left: 10px; color: #333; } .body { padding: 40px 20px; text-align: center; } .foot { height: 50px; text-align: center; button { margin-right: 20px; &:last-child { margin-right: 0; } } } </style>
这里只是写了简单的功能,并没有考虑更复杂的情况,比如按钮颜色定制,大小定制,z-index层级的考虑,遮罩层的统一管理等等,只是为了掌握编写弹出框的主要思想,所以没有写太多的情况。这里只细分了是确认框还是通知框,可以定制弹出框的内容、标题等一些简单的常规操作。
其实这个组件写好,就可以在页面用起来了,直接在对应页面插入这段,可以也可以用:
<!--template--> <button @click="showAlert">点我</button> <alert ref="alert">我是一个确认框</alert> <!--javascript--> ... methods: { showAlert() { this.$refs.alert.show(); } } ...
当然,如果真要这么用,这个组件还是需要修改一些东西的,比如事件抛出,当点击确定或者取消按钮的时候,需要emit对应的事件,以提供给父组件捕获,并做相应的处理。
动态插入到页面中
为了能让组件动态的插入到页面中,需要对上面的组件进行封装,利用Vue.extend机制,可以轻松的做到这种封装,直接上代码:
import Vue from 'vue'; import alert from './alert'; const AlertConstructor = Vue.extend(alert); const div = document.createElement('div'); AlertConstructor.show = (options) => { document.body.appendChild(div); options.type = 'inform'; const propsData = Object.assign({}, options); const alertInstance = new AlertConstructor({ propsData, }).$mount(div); alertInstance.show(); }; AlertConstructor.confirm = (options) => { document.body.appendChild(div); options.type = 'confirm'; const propsData = Object.assign({}, options); const alertInstance = new AlertConstructor({ propsData, }).$mount(div); alertInstance.show(); }; export default AlertConstructor;
这里,show对应的是通知框,confirm对应的是确认框。我知道这种封装有点简单了,有很多情况没有考虑,比如有多个弹出框时的处理等。这里只是做了简单的封装,为的就是让大家明白此种封装主要思路是什么。
总结