一、介绍
什么是命令式(也可以称为函数式)弹窗呢?通常我们在vue的开发中想要使用弹窗,会先在中定义好元素,然后利用变量来控制dialog的显示隐藏,这种属于声明式弹窗。但是在一些场景中,比如我们封装的一些工具函数、hooks、插件等中没办法像在单文件组件中一样将dialog写到template中,或者一些公共的弹窗使用声明式弹窗会比较臃肿,这个时候命名式的调起弹窗就派上用场了。
二、常规声明式弹窗使用方法
下面是最常见的弹窗使用方法,我们借用element-plus的弹窗组件来说明。其实这种常规的方式本身也是有一些缺点的。比如每个Dialog都需要为其创建单独的变量去控制它的显示隐藏,如果只是额外维护一个变量这也不是不能接受,可是当同样的Dialog组件,即需要在父组件控制它的展示与隐藏,又需要在子组件中控制,这样整个项目代码里面就会出现很多这种变量,显得很冗余。
typescript
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'
const dialogVisible = ref(false)
</script>
<template>
<el-button plain @click="dialogVisible = true">
Click to open the Dialog
</el-button>
<el-dialog
v-model="dialogVisible"
:before-close="handleClose"
>
<span>This is a dialog content</span>
</el-dialog>
</template>
el-dialog本身是不支持命令式调用的。
三、命令式弹窗
命令式弹窗应该是什么样的形式呢?我们拿element-plus来说明。
typescript
<script lang="ts" setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import type { Action } from 'element-plus'
const open = () => {
ElMessageBox.alert('This is a message', 'Title', {
confirmButtonText: 'OK',
callback: (action: Action) => {
ElMessage({
type: 'info',
message: `action: ${action}`,
})
},
})
}
</script>
<template>
<el-button plain @click="open">Click to open the Message Box</el-button>
</template>
ElMessageBox.alert()这种形式就是一个命令式弹窗的调用方式,但是ElMessageBox只能支持简单的内容弹窗。当我们的弹窗内容比较复杂时如何使用命令式弹窗呢?
这个时候就需要我们自己封装一些方法来比较优雅/方便的使用弹窗。
命令式弹窗理想的使用效果
typescript
<script setup lang="ts">
import { ElButton } from 'element-plus';
import Comp from 'components/Comp.vue';
import MyDialog from 'components/MyDialog.vue';
// 不需要额外的变量控制弹窗
const handleOpenDialog = () => {
// 处理 MyDialog
};
</script>
<template>
<div>
<ElButton @click="handleOpenDialog"> 打开弹窗 </ElButton>
<div>其他内容。。。 </div>
</div>
// 不需要将MyDialog声明到template中
</template>
四个重点:
1、父组件使用Dialog不需要额外的变量控制;
2、不需要将Dialog声明到template中;
3、Dialog组件可以以单独的单文件组件形式(.vue)进行封装。这一点其实很重要,因为这是最简单的弹窗组件封装形式;
Dialog的处理可以直接在函数中进行。
四、可能的实现方法
命令式Dialog的实现方法有很多,先来列举一些常用的实现方法。
方法一
typescript
// showMyDialog.ts文件
import { createApp } from 'vue'
import MyDialog from 'components/MyDialog.vue';
const showMyDialog = () => {
const div = document.createElement('div');
document.body.appendChild(div);
const app = createApp(MyDialog);
app.mount(div)
}
export default showMyDialog
MyDialog组件与showMyDialog是两个文件,增加了维护的成本。
方法二
利用.tsx文件特性,将Dialog和showDialog合并到一个文件。 同时利用@styils/vue来方便写元素的样式。
typescript
// MyDialog.tsx文件。
import { createApp } from "vue";
import { ElButton } from "element-plus";
import { styled } from "@styils/vue";
const DivModal = styled('div', {
position: 'fixed',
width: '100%',
height: '100%',
// 其他css
});
const DivBox = styled('div', {
display: 'flex',
minWidth: '25%',
});
const DivText = styled('div', {
marginBottom: '1em'
});
const DialogBox = {
props: {
msg: {
type: String,
required: true
},
},
render(ctx: any) {
const { $props, $emit } = ctx;
return (
<DivModal class= "modal" >
<DivBox class="box" >
<DivText class="text" > { $props.msg } </DivText>
<div onClick = { $emit('onClick(e)') } >
<ElButton type="primary" > 确 定 </ElButton>
</div>
</DivBox>
</DivModal>
);
},
};
export function showDialog(props) {
const div = document.createElement("div");
document.body.appendChild(div);
const app = createApp(DialogBox,
{
...props,
}
);
app.mount(div);
};