深浅主题
组件重复挂载与事件泄漏
问题现象
每次进入页面后点击一次却执行多次,离开页面仍轮询,内存随切换次数增加。开发模式热更新应与真实导航分开验证。
适用环境
Vue 3、路由切换、KeepAlive、全局事件与定时器。
本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。
复现或诊断步骤
- 在 mounted、unmounted、activated、deactivated 中记录实例编号。
- 检查 router-view 的 key、v-if 和重复页面出口,确认是否主动创建了多个实例。
- 检查 window、事件总线、ResizeObserver 和 setInterval 的注册与清理是否对称。用 Performance 和 Memory 比较多次进入后的存活对象。
原因分析
移除事件时传入了新的匿名函数,无法匹配原处理器;KeepAlive 停用不等同卸载,因此仅 onUnmounted 清理的轮询会继续执行。持续变化的 key 也会主动重建组件。
修复示例
ts
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
let timer: ReturnType<typeof setInterval> | undefined
const onResize = () => { /* 测量当前容器 */ }
const start = () => {
if (timer) return
window.addEventListener('resize', onResize)
timer = setInterval(() => { /* 刷新只读数据 */ }, 30000)
}
const stop = () => {
window.removeEventListener('resize', onResize)
if (timer) clearInterval(timer)
timer = undefined
}
onMounted(start)
onActivated(start)
onDeactivated(stop)
onUnmounted(stop)将资源生命周期集中在一个 composable 中,start 和 stop 设计为可重复调用。异步完成后创建的监听,也必须登记清理函数,避免组件已经离开才注册。
验证方法
在生产构建中进入、离开十次,确认当前实例只有一个轮询任务、一次事件响应。停止后观察 Network 至少一个轮询周期,确认不再有后台请求。
常见误区
不要盲目删除 KeepAlive;不要通过移除全部全局监听来修复单个组件,这可能破坏其他页面。开发热更新产生的日志也不能直接证明生产环境泄漏。