watchEffect 的核心优势是自动追踪依赖并提供 onInvalidate 机制,在副作用重新执行前清理上一次未完成操作,如定时器、请求或事件监听;首次执行不触发 onInvalidate,后续依赖变化前及组件卸载时均会触发。

watchEffect 的核心优势之一,就是能自动追踪依赖并提供 onInvalidate 机制,在副作用重新执行前主动清理上一次未完成的操作——比如重复开启的定时器、未响应的请求、未移除的事件监听等。这在搜索框防抖、异步加载、轮询等场景中非常关键。
为什么需要清理定时器?
不清理会导致多个定时器同时运行,出现“多次触发”“结果错乱”“内存泄漏”等问题。例如用户快速输入“abc”,若每次输入都启动一个 1 秒定时器去请求,最终可能触发三次请求,但只有最后一次结果有意义,前两次应被取消。
onInvalidate 是怎么工作的?
它不是在组件卸载时才调用,而是在下一次 watchEffect 执行前立即触发,确保旧任务被中断。注意:
– 第一次执行 watchEffect 时,不会调用 onInvalidate;
– 后续每次因依赖变化而重新执行前,先运行 onInvalidate 里的清理逻辑;
– 组件销毁或手动 stop 时,也会触发一次 onInvalidate。
防抖搜索 + 定时器清理实战写法
以输入关键词触发延迟请求为例:
- 用 ref 管理搜索关键词:
const keyword = ref('') - 在 watchEffect 回调中启动定时器,并把 timer 句柄保存下来
- 通过 onInvalidate 清除上一个 timer,避免堆积
代码示例:
const keyword = ref('')
let timer = null
watchEffect((onInvalidate) => {
// 每次 keyword 变化,先清除上一轮定时器
onInvalidate(() => {
if (timer) clearTimeout(timer)
})
// 启动新定时器(模拟防抖 500ms)
timer = setTimeout(() => {
console.log('发起请求:', keyword.value)
// 这里可调用 api.search(keyword.value)
}, 500)
})
更安全的写法:避免全局变量污染
上面用了 let timer,其实推荐把 timer 写在回调内部,靠闭包保持引用,更符合响应式逻辑:
- 每次 watchEffect 执行都是独立作用域,timer 是局部变量
- onInvalidate 能访问到当前闭包内的 timer,无需担心跨次干扰
- 组件销毁时,timer 自然被 GC,配合 onInvalidate 更稳妥
优化后写法:
watchEffect((onInValidate) => {
const timer = setTimeout(() => {
console.log('请求关键词:', keyword.value)
}, 500)
onInValidate(() => {
clearTimeout(timer)
})
})
不复杂但容易忽略

















