watchEffect 中用 onCleanup 清理资源的关键是每次副作用重执行前主动中断上一轮操作,防止资源堆积、状态错乱和内存泄漏;定时器用闭包保存局部变量并配合 onCleanup,网络请求用 AbortController 同步注册清理,事件监听、第三方实例等也需对应释放。

在 watchEffect 中用 onCleanup 清理定时器或网络请求,关键不是“等组件卸载再处理”,而是每次副作用重新执行前就主动中断上一轮操作——防止资源堆积、状态错乱、内存泄漏。
清理定时器:闭包 + onCleanup 最简洁安全
不要把定时器存到 ref 或全局变量里。直接在 watchEffect 回调内声明局部变量,靠闭包让 onCleanup 能访问到当前有效的 timer:
- 每次响应式依赖变化,
onCleanup先执行,清除上一个setTimeout或setInterval - 接着启动新定时器,逻辑独立、互不干扰
- 组件销毁时,timer 自动被垃圾回收,
onCleanup也会触发一次(如果还没被覆盖过)
示例(防抖搜索):
const keyword = ref('')watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log('请求关键词:', keyword.value)
}, 300)
onCleanup(() => clearTimeout(timer))
})
取消网络请求:AbortController 是标准解法
浏览器原生支持,fetch 和 Axios(1.5+)都兼容 signal。核心是三步同步完成:
- 先创建
AbortController - 立刻注册
onCleanup(() => controller.abort()) - 再发起请求,并把
controller.signal传进去
这样哪怕请求已发出但还没返回,下一次 watchEffect 触发时,onCleanup 就能立即中止它。
注意捕获错误时跳过 AbortError:
if (err.name !== 'AbortError') {
// 处理真实错误,比如 404、超时、网络断开
}
})
其他常见资源也得一并清理
凡是“开了没关”的,都可能成为隐患:
-
事件监听器:用
addEventListener后,onCleanup里对应调removeEventListener -
第三方实例:ECharts、Mapbox、Three.js 等,初始化后保存实例引用,在
onCleanup中调.dispose()或.destroy() -
手动轮询:用
setInterval做后台刷新,必须配clearInterval,否则几十个定时器并发跑满 CPU
不能这么写:async/await 直接套 watchEffect
watchEffect(async () => { ... }) 是典型错误:
- 依赖收集在
await前就结束了,后续响应式变化不会触发重跑 -
onCleanup可能根本没注册上,导致请求永远悬着 - 容易引发无限循环或状态覆盖
正确做法:异步逻辑封装成函数,在 watchEffect 内同步调用,清理逻辑写在发起请求之前。










