全局弹窗
全局弹窗(Global Confirm / Sonner)的实现模式与代码示例
#type / howto
#status / evergreen
#tech / dev / component-patterns
[!info] related notes
全局弹窗 (Global Confirm / Sonner)
- 背后的核心模式:单例模式 (Singleton) + 观察者模式 (Observer)
目标
在 Vue/React 应用中实现全局弹窗,任何组件均可调用,无需手动管理 DOM 挂载与销毁。
前置条件
- Vue 3 + Composition API 或 React 18+
- 已安装 UI 库(如 Element Plus / shadcn-ui / Sonner)
步骤
1. 单例挂载(App.vue / App.tsx)
在根组件挂载唯一实例,确保全局只有一个弹窗 DOM 节点:
<!-- Vue 3 -->
<template>
<router-view />
<GlobalConfirmDialog />
<Toaster position="top-center" />
</template>
// React
function App() {
return (
<>
<Router />
<Toaster position="top-center" />
</>
);
}
2. 封装 useConfirm composable
// composables/useConfirm.ts
import { ref } from 'vue';
const isVisible = ref(false);
const message = ref('');
let resolvePromise: ((value: boolean) => void) | null = null;
export function useConfirm() {
function confirm(msg: string): Promise<boolean> {
message.value = msg;
isVisible.value = true;
return new Promise<boolean>((resolve) => {
resolvePromise = resolve;
});
}
function handleConfirm() {
isVisible.value = false;
resolvePromise?.(true);
}
function handleCancel() {
isVisible.value = false;
resolvePromise?.(false);
}
return { isVisible, message, confirm, handleConfirm, handleCancel };
}
3. 业务组件调用
<script setup>
import { useConfirm } from '@/composables/useConfirm';
const { confirm } = useConfirm();
async function handleDelete() {
const ok = await confirm('确定要删除该记录吗?');
if (ok) {
await deleteRecord(id);
}
}
</script>
4. Sonner / Toast 模式
import { toast } from 'sonner';
// 成功提示
toast.success('保存成功');
// 错误提示
toast.error('网络异常,请重试');
// Promise 模式
toast.promise(saveData(), {
loading: '保存中...',
success: '保存成功',
error: '保存失败',
});
验证
- 在任意深层组件调用
confirm()或toast.success(),确认弹窗/提示正常显示 - 打开 DevTools Elements 面板,确认全局只有一个弹窗 DOM 节点
- 快速连续调用,确认 Promise 各自独立 resolve,不会串扰