onCleanup在两种情况下触发:一是当前副作用被新执行覆盖前同步清理上一轮注册的函数,二是组件卸载时清理仍活跃的副作用;它绑定到单次watchEffect执行,仅清理上一轮,不保证每次effect结束都执行。

onCleanup 的触发时机其实就两种明确情况:当前副作用还没执行完,但马上要被新一次执行覆盖;或者组件即将卸载,而这个副作用还在活跃状态。
什么时候算“下一次副作用执行前”
当 watchEffect 依赖的响应式数据发生变化,Vue 会准备重新运行整个回调函数。在新回调开始执行之前,它会先调用上一轮注册的 onCleanup 函数——不是等旧回调跑完,而是“打断”它,清理掉可能残留的异步任务。
- 比如搜索框输入 “a” → 发起请求 A;紧接着输入 “ab” → 请求 A 还没返回,但 watchEffect 已检测到依赖变化,立刻触发 onCleanup 把 A 取消,再发起请求 B
- 这个过程不等待 A 的 .then 或 .catch,清理是同步、即时的,确保不会出现“后发的请求反而先更新状态”的竞态问题
组件卸载时也会触发
如果依赖一直没变,watchEffect 回调只执行了一次,那 onCleanup 就等到组件 unmount 时才运行。前提是该副作用实例还存在(没被提前停止)。
- 例如监听 window.resize 并做节流处理,组件关闭前必须 removeEventListener,否则事件监听器会持续占用内存
- 即使你手动调用了 stop() 停止侦听器,onCleanup 也不会再执行——它只属于当前激活的副作用生命周期
关键细节:它只清理“上一轮”注册的内容
每次 watchEffect 执行,都会新建一个副作用实例,onCleanup 也只绑定到当前这一轮。上一轮注册的清理函数,在下一轮开始前自动调用;而本轮注册的,留到再下一轮或卸载时才用。
- 不能靠它做“每次 effect 结束后都执行”的逻辑,它不是 finally,也不是 effect 的收尾钩子
- 如果你在一次回调里多次调用 onCleanup,只有最后一次注册的会生效(前面的会被覆盖)
不复杂但容易忽略


















