需求背景
很多情况下因为业务的要求,我们可能需要在第三方组件库的基础上进行二次封装,使其功能或样式更加符合我们的业务场景
举一个简单的例子,假设我们需要在 vant-dialog 定制自己的内容,比如说在右上角加个关闭按钮,如果正常的引用组件方法,我们需要在每个使用的页面引用组件的结构,这样比较麻烦
// 引用组件的页面
<van-dialog v-model:show="show" title="标题" show-cancel-button>
<img src="https://fastly.jsdelivr.net/npm/@vant/assets/apple-3.jpeg" />
</van-dialog>
import { ref } from 'vue';
export default {
setup() {
const show = ref(false);
return { show };
},
};
我们期望这样使用, 使用函数式直接调用那就方便不少
import { showMyDialog } from '@/components/showMyDialog'
export default {
setup() {
showMyDialog({ msg: '弹窗内容' })
},
};
怎么实现呢? 主要分为两步:
- 封装 MyDialog 组件
- 实现 showMyDialog 函数
封装 vant-dialog 弹窗
二次封装 vant-dialog,思路基本就是根据自己的业务需求对样式或功能进行定制,我们的场景并不需要对所有属性进行透传,这里根据自己的实际情况进行改造就好
<template>
<van-dialog
class="my-dialog"
v-model:show="localShow"
:show-cancel-button="showCancelButton"
@confirm="handleConfirm"
@cancel="handleCancel">
<div class="dialog-wrap">
<div class="header">
<van-icon @click="handleCancel" class="close-btn" name="close" />
</div>
<div class="content">
<p>{{msg}}</p>
</div>
</div>
</van-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
interface Props {
visible: boolean
_close?: () => void
_confirm?: () => void
msg: string
showCancelButton?: boolean
}
const props = defineProps<Props>()
// 将外部传入的 visible 状态转换为本地响应式变量
const localShow = ref(props.visible)
const emit = defineEmits(['update:visible'])
const handleConfirm = () => {
props._confirm?.()
emit('update:visible', false)
}
const handleCancel = () => {
emit('update:visible', false)
props._close?.()
}
</script>
封装 showMyDialog 函数
-
实现 showMyDialog 函数:
-
创建一个 DOM 节点(
mountNode),用于挂载新的 Vue 应用实例。 -
使用
createApp函数创建一个新的 Vue 应用实例,并将MyDialog.vue作为根组件,传入的 props 包括:- 设置
visible属性为true使对话框初始化时可见 - 将外部传入的
msg、type和showCancelButton分配给组件相应的 prop _close和_confirm回调函数分别处理点击关闭和确认操作,在内部进行卸载应用实例、移除 DOM 节点等清理工作,并触发外部提供的close或confirm回调函数(如果存在)
- 设置
-
将新创建的
mountNode添加到文档的body元素中,确保对话框能正确显示在页面上。 -
最后,调用
mount方法将 Vue 应用实例挂载到mountNode上,完成弹窗组件的渲染。
-
import { type App, createApp } from 'vue'
import MyDialog from './MyDialog.vue'
interface IMyDialog {
close?: () => void
confirm?: () => void
msg: string
showCancelButton?: boolean
}
export function showMyDialog({ close, confirm, msg, type, showCancelButton }: IMyDialog) {
let mountNode = document.createElement('div')
let dialogApp: App<Element> | undefined = createApp(MyDialog, {
visible: true,
msg,
type,
showCancelButton,
_close: () => {
if (dialogApp) {
dialogApp.unmount()
document.body.removeChild(mountNode)
dialogApp = undefined
close?.()
}
},
_confirm: () => {
confirm?.()
dialogApp?.unmount()
document.body.removeChild(mountNode)
dialogApp = undefined
},
})
document.body.appendChild(mountNode)
dialogApp.mount(mountNode)
}
使用
import { showMyDialog } from '@/components/showMyDialog'
showMyDialog({ msg: '测试弹窗', confirm: () => { console.log('点击了确认'); }})