Vue 3 中直接调用 window.alert 是错误起点,会导致 SSR 报错、阻塞线程、样式不可控;应封装支持 v-model:visible、插槽内容、Esc/遮罩关闭及 onBeforeUnmount 清理的 AlertModal 组件。

Vue 3 中用 window.alert 就是错的起点
直接在 Vue 组件里调用 window.alert 看似简单,实则破坏响应式、阻塞主线程、无法定制样式,且在 SSR(如 Nuxt)中会报 ReferenceError: window is not defined。这不是“能用就行”,而是埋了三个雷:服务端渲染失败、UI 不可控、交互体验僵硬。
真正该做的,是封装一个可复用、可关闭、能响应数据变化的轻量提示组件,而不是包装一层 alert。
封装 AlertModal 组件的关键三件事
它不是弹窗库的替代品,而是解决“临时确认/提示”这类高频低复杂度场景。重点不在炫技,而在不侵入业务逻辑、不强制状态管理、不依赖第三方。
- 用
v-model:visible双向绑定控制显隐,符合 Vue 3 通用习惯,父组件只需传:visible.sync或用v-model - 内容通过
default插槽注入,避免字符串拼接 HTML,支持内嵌<strong></strong>、变量插值和简单指令 - 点击遮罩或按
Esc自动关闭,但必须加@click.self防止点到弹窗内部误关 —— 这是最多人漏掉的细节
<AlertModal v-model:visible="showAlert">
<p>你确定要删除 <strong>{{ itemName }}</strong> 吗?</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/cb6835dc7db1" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">前端免费学习笔记(深入)</a>”;</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill10651" title="蓝湖代码转vue2.0+element-ui"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20e695c2374.jpg" alt="蓝湖代码转vue2.0+element-ui" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill10651" title="蓝湖代码转vue2.0+element-ui">蓝湖代码转vue2.0+element-ui</a>
<p>将静态 Vue 页面(index.vue)改造为动态页面(Custom.vue);稿源仅限 index.vue、其引用的 CSS 与资源路径,禁止参考仓库内其他文件。Invoke when user wants to transform a static Vue page into a dynamic data-driven page with Element UI components.</p>
</div>
<a href="/xiazai/skill10651" title="蓝湖代码转vue2.0+element-ui" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div>
</AlertModal>
onBeforeUnmount 里清理定时器和事件监听器
如果组件内部用了 setTimeout 自动关闭,或 keydown 监听 Esc,卸载时没清理就会导致内存泄漏或触发已销毁组件的回调 —— 表现为控制台报 Cannot read property 'xxx' of null,尤其在路由跳转后还弹出旧提示。
正确做法是在 onBeforeUnmount 中统一清除:
- 保存
setTimeout的返回值,用clearTimeout清掉 - 用
addEventListener绑定的keydown,必须配对调用removeEventListener - 不要依赖
v-if切换就万事大吉 ——v-if=false时组件已卸载,但监听器可能还在
不推荐用 Teleport 到 body 的真实原因
很多教程一上来就用 <teleport to="body"></teleport> 把弹窗挂到 document 根节点,理由是“脱离父级样式污染”。但实际项目中,90% 的警告对话框根本不需要这个层级穿透能力 —— 它们只在当前业务模块内使用,且父容器已设好 position: relative 和 z-index。
硬套 Teleport 带来两个麻烦:
- 样式调试变困难:浏览器开发者工具里找不到组件对应的真实 DOM 位置
- 焦点管理失控:关闭后焦点没回到触发按钮上,用户按
Tab会跳过关键操作入口 - 如果父组件用了
overflow: hidden(比如抽屉、卡片),反而需要Teleport;但这是例外,不是默认路径
先让组件在局部正常工作,再根据具体布局问题决定是否提升挂载点。


















