vue.extend()+aysnc封装一个弹层组件【vue2】
介绍
通过这篇文章,你将掌握:
- vue.extend
- async + await在vue中的使用
- $mount动态将组件挂载到body下
- 收获一个支持vue2的弹层组件(vue3由于更换api,需要修改,本文暂不进行说明)。
封装弹层组件
首先要封装一个弹层组件confirm.vue
// 模板
<template>
<transition name="confirm-fade">
<div class="cofirm" v-if="show">
<div class="cofirm-inner" @click.stop>
<div class="head">
<p class="head-text"><em>{{content.headText ? content.headText : '系统提示'}}</em></p>
<div class="close" @click="cancel">×</div>
</div>
<div class="body">
<div class="content">{{content.title}}</div>
<div class="tips-btn">
<button
type="button"
v-if="content.confirm"
@click="confirm"
class="btn confirm"
>
{{content.confirm}}
</button>
<button type="button"
v-if="content.cancel"
@click="cancel"
class="btn"
>{{content.cancel}}</button>
</div>
</div>
</div>
</div>
</transition>
</template>
// JS
export default {
data(){
return {
// 决定是否显示
show :false,
// 弹窗的内容
content: {
headText: '',
title:'',
cancel:'',
confirm:''
}
}
},
methods:{
confirm(){
return true;
},
cancel(){
return false;
}
}
};
<style lang="less" rel="stylesheet/less" scoped>
@confirm-dialog-bg: rgba(46, 62, 78, 0.85);
@confirm-gray-lite: #e6e6e6;
@confirm-brand: #518ACA;
@confirm-gray-dark: #4d4d4d;
@confirm-gray-middle: #dedede;
@keyframes confirmInDown {
0% {
opacity: 0;
transform: translate(-50%, -80%);
}
100% {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.cofirm{
background-color: @confirm-dialog-bg;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
user-select: none;
}
.confirm-fade-enter-active,
.confirm-fade-leave-active {
transition: opacity 0.5s ease;
}
.confirm-fade-enter, .confirm-fade-leave-to {
opacity: 0;
}
.cofirm-inner {
position: absolute;
background-color: #fff;
overflow: hidden;
border-radius: 5px;
box-shadow: 0 3px 3px rgba(0,0,0, 0.4);
width: 360px;
left: 50%;
top: 50%;
animation: confirmInDown 0.4s both;
.head{
display: flex;
justify-content: space-between;
border-bottom: 1px solid @confirm-gray-lite;
height: 40px;
padding: 0 15px;
font-size: 14px;
line-height: 40px;
color: darken(@confirm-gray-lite, 30%);
.head-text{
font-size: 0;
em{
font-size: 14px;
display: inline-block;
}
}
.head-text::before{
content: "!";
display: inline-block;
font-size: 12px;
border: 1px solid;
border-radius: 50%;
vertical-align: 2px;
width: 14px;
height: 14px;
line-height: 14px;
margin-right: 5px;
text-align: center;
}
.close {
text-align: right;
font-size: 20px;
color: darken(@confirm-gray-lite, 20%);
cursor: pointer;
&:hover{
color: @confirm-brand;
}
}
}
.body{
padding: 15px;
.content {
font-size: 16px;
color: @confirm-gray-dark;
letter-spacing: 0;
line-height: 24px;
text-align: left;
padding: 10px 0 25px;
}
.tips-btn{
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
.btn{
padding: 0 22px ;
height: 36px;
line-height: 36px;
border-radius: 6px;
background-color: #fff;
border: 1px solid @confirm-gray-middle;
&:hover{
background-color: @confirm-gray-lite;
}
& + .btn{
margin-left: 10px;
}
&.confirm{
background-color: @confirm-brand;
color: #fff;
border: 1px solid @confirm-brand;
&:hover{
background-color: darken(@confirm-brand, 10%);
}
}
}
}
}
}
</style>
编写引用的文件(重点)
以我的目录为例,在src/common/js/confirm.js,新建js文件
先引入vue,将组件添加进Vue中
import Vue from 'vue';
// 引用组件
import Confirm from 'components/confirm/confirm';
// 使用基础 Vue 构造器,创建一个“子类”。
// 参数是一个包含组件选项的对象。
const confirmConstructor = Vue.extend(Confirm);
将Confirm组件创建为一个子类,如果直接挂在的话,执行以下代码会直接挂载到<div id="demo"></div>
new confirmConstructor().$mount('#demo');
如果newconfirmConstructor().$mount(); 那么就不会直接挂载在DOM上,而是先实例化成一个组件对象。($mount()为手动挂载 带元素id为挂载到指定元素上,不加元素不渲染, 只是实例化该组件)
将点击交互封装为Promise
let confirmer = function (msg) {
return new Promise((resolve, reject) => {
// 將组件实例化
let confirmEl = new confirmConstructor().$mount();
// 通过confirmEl.$el获取组件的DOM元素 并插入到body下
document.body.appendChild(confirmEl.$el);
// 如果每一项都要设置就使用这种方式,传值的时候就传一个对象
// confirmEl.content = content;
// 给组件的data->content属性进行赋值
confirmEl.content = {
title: msg,
confirm: '确定',
cancel: '取消'
};
confirmEl.show = true;
// 监听确定的事件
confirmEl.confirm = function () {
resolve(true);
confirmEl.show = false;
};
// 接收取消的事件
confirmEl.cancel = function () {
reject(false);
confirmEl.show = false;
};
});
};
这里需要特别注意的是,需要将点击事件,作为一个promise对象返回。这样就可以使用.then()进行链式调用和使用async+awit将这个“异步”交互转为同步!
在这里,$mount还有另外一种写法(他们的作用都是相同的):
let confirmEl = new confirmConstructor().$mount();
// 等价于
let confirmEl = new confirmConstructor({
//实例化后要绑定的元素
el: document.createElement('div')
});
// 如官网案例中new Vue时
new Vue({ el: '#app', data: {...}})
// 等同于
new Vue({ data: {...} }).$mount('#app')
async+await同步异步操作
async function confirmChoose(content) {
let sure = false;
try {
// await后面是一个promise对象
sure = await confirmer(content);
} catch (e) {
// 捕获到错误 转为立即resolve的promise对象
sure = false;
}
return sure;
}
使用async将异步操作阻断,等待(await)操作执行。注意 async与await必须成对出现!!
封装alert作用的组件
// 这个仅仅做提示用,无需根据返回值判断
let alertChoose = function(msg) {
let confirmEl = new confirmConstructor({
// 实例化后要绑定的元素
el: document.createElement('div')
});
document.body.appendChild(confirmEl.$el);
confirmEl.show = true;
confirmEl.content.title = msg;
confirmEl.content.confirm = '确定';
confirmEl.confirm = function () {
confirmEl.show = false;
};
confirmEl.cancel = function () {
confirmEl.show = false;
};
};
因为alert作用的弹层,无需根据点击按钮是哪个进行判断,只要点击,就隐藏弹层就行了。所以只改变show的状态即可。
完整代码
import Vue from 'vue';
import Confirm from 'components/confirm/confirm';
const confirmConstructor = Vue.extend(Confirm);
let confirmer = function (msg) {
return new Promise((resolve, reject) => {
let confirmEl = new confirmConstructor().$mount();
document.body.appendChild(confirmEl.$el);
confirmEl.content = {
title: msg,
confirm: '确定',
cancel: '取消'
};
confirmEl.show = true;
confirmEl.confirm = function () {
resolve(true);
confirmEl.show = false;
};
confirmEl.cancel = function () {
reject(false);
confirmEl.show = false;
};
});
};
let alertChoose = function(msg) {
let confirmEl = new confirmConstructor({
el: document.createElement('div')
});
document.body.appendChild(confirmEl.$el);
confirmEl.show = true;
confirmEl.content.title = msg;
confirmEl.content.confirm = '确定';
confirmEl.confirm = function () {
confirmEl.show = false;
};
confirmEl.cancel = function () {
confirmEl.show = false;
};
};
async function confirmChoose(content) {
let sure = false;
try {
sure = await confirmer(content);
} catch (e) {
sure = false;
}
return sure;
}
export { confirmChoose, alertChoose };
在main.js中引用
// 引入封装的方法
import { confirmChoose, alertChoose } from "common/js/confirm";
// 将方法挂载到vue的原型链上 使用:this.$confirm(msg)
Vue.prototype.$confirm = confirmChoose;
// 挂载到window上,直接覆盖原来的方法 使用 window.alert(msg)
window.alert = alertChoose;
// 可以先保存旧的弹窗备用
window.alertOld = window.alert;
// 再将原本的弹窗进行覆盖
window.alert = alertChoose;
如何使用
使用单按钮弹层
使用单按钮弹层,最简单不过了,直接在点击的时候执行window.alert('我的信息')即可。如
if (this.arr.includes(url)) {
window.alert('抱歉,请不要提交重复的视频地址!');
return;
}
使用询问框弹层
使用的时候有两种方式,先说我推荐的使用方式吧。
使用async+await
前面说过了,使用async+await必须成对出现!如在一个组件中,执行点击删除,出现询问框的操作。
methods: {
async deleteResourceItem(url, index, type) {
let sure = await this.$confirm('确定要删除当前资源吗?');
if (!sure) {
return false;
}
...
}
}
需要在函数的名称前面加上async!!他的作用就是告诉浏览器,这里面有个异步操作,需要转为同步!
然后在返回promise的异步操作前面加上await将异步转为同步操作!
这样就可以根据点击的按钮接收到返回的值了。
还有一些操作是在异步操作之中使用async的情况,来看一个例子:
methods: {
getDetailInfo() {
getInfo(url).then(async res => {
// 判断是否是本站的链接
if (istrue) {
let sure = await this.$confirm(msg);
if (sure) {
this.dosomething();
}
} else {
window.alert(...);
}
}).catch(err => {
window.alert(....);
}).finally(() => {
// 始终都执行
});
}
}
async+await的使用原则就是就近原则!哪个函数里面有异步操作需要转化为同步的,就在哪个函数外面加async,等待哪个异步操作,就在操作之前加await。如果遇到报错:await is a reserved word,是因为async没放到应用await的函数上,async和await是成对出现的,就算匿名函数也要加上。加上async的函数会被await阻塞,await会跳出async让出线程。
使用then
this.$confirm('确定要删除当前资源吗?')
.then(res => {
// ..
}).catch(rej => {
// .. 捕获promise reject的错误
});