
本文详解如何在 Vue 3 中替代 Vue 2 的 new Vue({ extends: Component }) 和 $on/$destroy/$mount 模式,通过 createApp + Teleport + defineExpose 实现可编程调用的 Modal(如 showPrompt),兼顾类型安全、响应式控制与上下文完整性。
本文详解如何在 vue 3 中替代 vue 2 的 new vue({ extends: component }) 和 $on/$destroy/$mount 模式,通过 createapp + teleport + defineexpose 实现可编程调用的 modal(如 showprompt),兼顾类型安全、响应式控制与上下文完整性。
在 Vue 3 中,直接 new 组件实例、手动挂载、使用 $on 或 $destroy 已被彻底移除——这不仅是 API 变更,更是设计理念升级:Vue 3 推崇声明式、组合式、受控的组件生命周期管理,而非命令式 DOM 操作。因此,重构 showPrompt 函数的关键在于 放弃“手动实例化”,转向“动态挂载 + 显式暴露 + Promise 驱动”模式。
✅ 推荐方案:createApp + Teleport + defineExpose + Promise
步骤 1:改造 Modal 组件,显式暴露控制接口
<!-- @/layouts/Modal.vue -->
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
// 声明 props(支持 v-model:visible)
const props = defineProps<{
visible?: boolean
positiveButtonColor?: string
showPositiveButton?: boolean
contentSlotPaddingClass?: string
positiveButtonText?: string
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'confirm'): void
(e: 'cancel'): void
(e: 'closed'): void
}>()
const isVisible = ref(props.visible ?? false)
// 同步 prop.visible → internal state
watch(() => props.visible, (val) => {
isVisible.value = val ?? false
})
// 同步 internal state → parent via v-model
const updateVisible = (value: boolean) => {
isVisible.value = value
emit('update:visible', value)
}
// 暴露关键方法供外部调用
defineExpose({
show: () => updateVisible(true),
hide: () => updateVisible(false),
confirm: () => emit('confirm'),
cancel: () => emit('cancel'),
})
</script>✅ 优势:
defineExpose精准控制外部可访问能力;v-model:visible兼容父组件双向绑定;无副作用、可测试、类型完整。
步骤 2:实现类型安全的 showPrompt 工厂函数
// utils/modal.ts
import { createApp, Teleport, ref, onMounted, onUnmounted } from 'vue'
import Modal from '@/layouts/Modal.vue'
interface PromptOptions {
positiveButtonColor?: string
showPositiveButton?: boolean
contentSlotPaddingClass?: string
positiveButtonText?: string
// 扩展任意其他 props
[key: string]: any
}
export function showPrompt(options: PromptOptions = {}): Promise<'confirm' | 'cancel'> {
return new Promise((resolve) => {
// 1. 创建容器元素(挂载点)
const container = document.createElement('div')
container.className = 'modal-prompt-container'
document.body.appendChild(container)
// 2. 构建 props(含默认值与逻辑)
const props = {
...options,
positiveButtonColor: options.positiveButtonText === 'Yes'
? 'primary'
: options.positiveButtonColor ?? 'error',
showPositiveButton: options.showPositiveButton ?? true,
contentSlotPaddingClass: options.contentSlotPaddingClass ?? 'pa-0',
visible: true, // 初始显示
}
// 3. 创建独立 app 实例(注入必要插件,如 i18n/vuetify)
const app = createApp({
setup() {
const modalRef = ref<InstanceType<typeof Modal> | null>(null)
onMounted(() => {
// 确保 Modal 实例就绪后调用 show(防竞态)
setTimeout(() => {
modalRef.value?.show()
}, 0)
})
const handleConfirm = () => {
resolve('confirm')
cleanup()
}
const handleCancel = () => {
resolve('cancel')
cleanup()
}
const handleClose = () => {
// 支持 Modal 内部关闭(如点击遮罩)
resolve('cancel')
cleanup()
}
const cleanup = () => {
if (modalRef.value) {
modalRef.value.hide()
}
setTimeout(() => {
app.unmount()
container.remove()
}, 300) // 匹配 fade-out 动画时长
}
return () => h(
Teleport,
{ to: container },
h(Modal, {
...props,
ref: modalRef,
'onUpdate:visible': (v: boolean) => !v && handleClose(),
onConfirm: handleConfirm,
onCancel: handleCancel,
onClosed: handleClose,
})
)
}
})
// ⚠️ 关键:手动注入全局插件(如 Vuetify、i18n)
// app.use(vuetify).use(i18n) // 根据项目实际注入
// 4. 挂载
app.mount(container)
})
}✅ 使用示例(TypeScript 安全)
// 在任意 Composition API setup 中调用
async function handleDelete() {
try {
const result = await showPrompt({
title: '确认删除?',
content: '此操作不可撤销。',
positiveButtonText: 'Yes',
positiveButtonColor: 'error'
})
if (result === 'confirm') {
await api.deleteItem()
console.log('已删除')
}
} catch (e) {
console.error('Prompt 被中断', e)
}
}⚠️ 注意事项与避坑指南
-
不要尝试
new Modal():Vue 3 组件不是构造函数,无法new;defineComponent返回的是描述对象,非类实例。 -
避免丢失上下文:
createApp默认无插件,务必显式.use(...)注入i18n、vuetify、pinia等,否则组件内useI18n()将报错。 -
内存与 DOM 清理:必须
app.unmount()+container.remove(),否则造成内存泄漏和重复挂载。 -
事件监听替代
$on:Vue 3 使用onXXXprop(如onConfirm)或v-on:confirm,语义清晰且类型推导完备。 -
响应式 props 同步:通过
watch监听props.visible并同步isVisible,确保外部控制权不丢失。
✅ 总结
Vue 3 的 showPrompt 不再是“创建实例→绑定事件→手动挂载→手动销毁”的命令式流程,而是:
声明式定义行为(Modal 组件)→ 编程式触发(
showPrompt()返回 Promise)→ 响应式控制(v-model:visible+defineExpose)→ 自动清理(unmount+remove)立即学习“前端免费学习笔记(深入)”;
这一模式更符合 Vue 3 的响应式哲学,具备更好的可维护性、可测试性与 TypeScript 支持,也是官方文档与主流 UI 库(如 Vuetify 3、PrimeVue)推荐的 Modal 实现范式。


















