Skip to content

组件重复挂载与事件泄漏 ​

问题现象 ​

每次进入页面后点击一次却执行多次,离开页面仍轮询,内存随切换次数增加。开发模式热更新应与真实导航分开验证。

适用环境 ​

Vue 3、路由切换、KeepAlive、全局事件与定时器。

本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。

复现或诊断步骤 ​

  1. 在 mounted、unmounted、activated、deactivated 中记录实例编号。
  2. 检查 router-view 的 key、v-if 和重复页面出口,确认是否主动创建了多个实例。
  3. 检查 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;不要通过移除全部全局监听来修复单个组件,这可能破坏其他页面。开发热更新产生的日志也不能直接证明生产环境泄漏。

官方参考链接 ​

相关分类:前端 Web · 全部笔记