watchEffect 的清理操作必须在副作用函数同步执行阶段通过 onInvalidate 注册,仅在下轮执行前或组件卸载时触发,且 onInvalidate 仅限顶层同步调用;常见场景包括取消请求、清除定时器和移除事件监听。

watchEffect 的清理操作必须在副作用函数的同步执行阶段注册,通过传入的 onInvalidate(或 onCleanup)参数完成。它不是在每次 effect 执行完调用,而是在下一次执行开始前、或组件卸载时,自动触发上一轮未完成副作用的清理逻辑。
清理必须写在同步位置
onInvalidate 只能在 watchEffect 回调的顶层同步代码中调用。一旦进入 await、setTimeout、Promise.then 等异步上下文,Vue 已丢失当前响应式上下文,此时调用会报错 “no active effect found”。
- ✅ 正确:控制器创建、事件监听绑定、定时器赋值都发生在 onInvalidate 调用之前,且在函数体最外层
- ❌ 错误:把 onInvalidate 放在 await 后、或 setTimeout 回调里,已脱离响应式追踪生命周期
常见清理场景写法
清理的目标是中断“已发起但尚未完成”的副作用,避免竞态、内存泄漏和状态污染。
- 取消网络请求:每次触发新建 AbortController,将 signal 传给 fetch,onInvalidate 中调用 controller.abort()
- 清除定时器:用 let timer 声明变量,在新定时器启动前,onInvalidate 清掉旧 timer
- 移除事件监听:定义 handler 函数并绑定到 window 或 DOM 元素,onInvalidate 中执行 removeEventListener
触发时机很关键
onInvalidate 不是“每次执行后都运行”,而是只在以下两种情况触发:
立即学习“前端免费学习笔记(深入)”;
- 当前 watchEffect 因依赖变化即将重新执行——先清理上一轮,再跑新的
- 组件被卸载,且该 watchEffect 尚未被新执行覆盖——也会触发一次清理
注意:第一次执行 watchEffect 时,onInvalidate 不会被调用;它只清理“被中断的上一轮”,不处理“本轮”。
防抖搜索中的典型用法
用户快速输入时,每次 keyword 变化都应取消前一个延迟请求:
- 声明 let timer = null
- 在 watchEffect 内部,先 onInvalidate(() => clearTimeout(timer))
- 再 timer = setTimeout(..., 300),发起新延迟逻辑
这样能确保任意时刻最多只有一个定时器在运行,不会堆积或错乱。


















