一、前言
相信大家都封装过弹窗组件,基本思路都是父组件给子组件传递一个变量,子组件props进行接收,当点击确认或者关闭按钮时,通过emit回传事件供父组件调用。这种封装方式缺点是复用性查、使用频繁时需要定义多份{isVisible、handleSubmit、handleClose}代码,代码冗余,今天分享一种命令式组件封装的方式。
二、什么是命令式组件
命令式组件封装是一种将功能封装在组件内部,并通过命令式的方式进行调用和控制的封装方法。在命令式组件封装中,组件负责封装一定的功能逻辑,并提供一组接口或方法,供外部代码调用来触发和控制组件的行为。调用方式大致为:
async function showMsg(){
const [,res]= await msgBox('测试内容',{type:'success',iconType:'question'})
if(res){
consloe.log('点击了确定')
}else{
console.log('点击了取消')
}
}
三、开始封装
首先创建一个MessageBox.vue
文件,编写组件样式代码,这里我使用的是unocss
进行编写。
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue"
import { ElButton, ElDialog } from 'element-plus'
export interface HuiMsgBoxProp {
/** 控制图标展示类型 info:叹号 success:钩 question:问号 */
iconType:'info' | 'success' | 'question',
/** 控制图标展示的颜色 */
type:'info' | 'warning' | 'success' | 'danger',
/** 弹窗显示的内容 */
content:string,
/** 取消按钮的文本 */
cancelText:string,
/** 确定按钮的文本 */
confirmText:string,
/** 关闭事件 */
closeBox: ()=> void,
/** 确定事件事件 */
confirmHandler:()=> void,
/** 取消事件 */
cancelHandler:()=> void,
}
const { iconType, type, content, cancelText, confirmText, closeBox, confirmHandler, cancelHandler } = withDefaults(defineProps<HuiMsgBoxProp>(), {
iconType: 'info',
type: 'info',
cancelText: '取消',
confirmText: '确定',
})
const iconTypeClass = computed<string>(() => {
const iconTypeClassList = {
info: 'text-disabled',
warning: 'text-warning',
success: 'text-success',
danger: 'text-danger',
}
return iconTypeClassList[type]
})
const iconColorClass = computed<string>(() => {
const iconColorClassList = {
info: 'i-com-gantanhao',
success: 'i-com-gou1',
question: 'i-com-wenhao',
}
return iconColorClassList[iconType]
})
// 控制显示处理
const isVisible = ref(false)
/**
* 组件展示
*/
const show = () => {
isVisible.value = true
}
/**
* 处理动画 (render 函数的渲染,会直接进行)
*/
onMounted(() => {
show()
})
/**
* 取消事件
*/
const onCancelClick = () => {
if (cancelHandler) {
cancelHandler()
}
close()
}
/**
* 确定事件
*/
const onConfirmClick = () => {
if (confirmHandler) {
confirmHandler()
}
closeBox()
}
// 关闭动画处理时间
const duration = '0.5s'
/**
* 关闭事件,保留动画执行时长
*/
const close = () => {
isVisible.value = false
// 延迟一段时间进行关闭
setTimeout(() => {
if (closeBox) {
closeBox()
}
}, parseInt(duration.replace('0.', '').replace('s', '')) * 100)
}
</script>
<template>
<div class="hua5-message-box">
<ElDialog
v-model="isVisible"
width="400"
@closed="close"
>
<div class="flex justify-center flex-center h-110">
<div>
<i :class="[iconTypeClass,iconColorClass,'icon-com !text-27']" />
</div>
<div class="text-14 font-bold text-normal ml-11">{{ content }}</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton class="!text-primary !border !border-1 !border-primary" @click="onCancelClick">{{ cancelText }}</ElButton>
<ElButton type="primary" class="w-100px" @click="onConfirmClick">{{ confirmText }}</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<style lang="scss">
.hua5-message-box{
.el-dialog{
border-radius: 8px !important;
}
.el-dialog .el-dialog__header{
background-color: #fff !important;
}
.el-dialog .el-dialog__footer{
background-color: #fff !important;
}
}
</style>
组件需要接收参数
export interface HuiMsgBoxProp {
/** 控制图标展示类型 info:叹号 success:钩 question:问号 */
iconType:'info' | 'success' | 'question',
/** 控制图标展示的颜色 */
type:'info' | 'warning' | 'success' | 'danger',
/** 弹窗显示的内容 */
content:string,
/** 取消按钮的文本 */
cancelText:string,
/** 确定按钮的文本 */
confirmText:string,
/** 关闭事件 */
closeBox: ()=> void,
/** 确定事件事件 */
confirmHandler:()=> void,
/** 取消事件 */
cancelHandler:()=> void,
}
其次创建index.ts
文件,由于组件调用方式是通过一个函数进行调用的,并提供.then
和.catch
方法,所以需要编写一个函数,该函数返回一个Promise。当调用该函数,创建组件实例,组件进行挂载。
import { h, render } from 'vue'
import confirmComponent from './message-box.vue'
import { to } from "@hua5/hua5-utils"
export interface PayLoadType {
/** 控制图标展示类型 info:叹号 success:钩 question:问号 */
iconType?:'info' | 'success' | 'question',
/** 控制图标展示的颜色 */
type?: "info" | "success" | "danger" | "warning",
/** 取消按钮的文本 */
cancelText?:string,
/** 确定按钮的文本 */
confirmText?:string
}
export const hua5MsgBox = (content: string, payLoad:PayLoadType = {}) => {
const { iconType = 'info', type = 'info', cancelText, confirmText } = payLoad
return new Promise((resolve) => {
// 取消按钮事件
const cancelHandler = () => {
resolve(false)
}
// 确定按钮事件
const confirmHandler = () => {
resolve(true)
}
// 关闭弹层事件
const closeBox = () => {
render(null, document.body)
}
// 1. 生成 vnode
const vnode = h(confirmComponent, {
content,
iconType,
type,
cancelText,
confirmText,
cancelHandler,
confirmHandler,
closeBox,
})
// 2. render 渲染
render(vnode, document.body)
})
}
export const msgBox = (content: string, payLoad?:PayLoadType) => {
return to(hua5MsgBox(content, payLoad))
}
四、to函数
/**
* @param { Promise } promise
* @param { Object= } errorExt - Additional Information you can pass to the err object
* @return { Promise }
*/
export function to(promise: Promise<any>): Promise<any> {
return promise.then(res => [null, res]).catch(error => [error, null])
}