不能直接用 watcheffect 监听定时器 id,而应监听控制其行为的响应式状态;oninvalidate 在下次执行前或组件卸载时清理旧定时器;推荐闭包内声明 timer 并配合 oninvalidate 精准清理;轮询需响应式开关统一管理;flush 默认 'pre' 适合多数清理场景。

不能直接用 watchEffect 监听定时器 ID,因为它是普通数字,不具响应性。真正要监听的是控制定时器行为的响应式状态——比如是否激活、输入关键词、页面可见性等。清理的核心在于:每次新副作用启动前,必须取消上一次未完成的任务。
onInvalidate 是清理定时器的关键机制
onInvalidate 不是组件卸载时才调用,而是在下一次 watchEffect 执行前立即触发,确保旧定时器被清除。它天然适配防抖、轮询、搜索请求等场景:
- 第一次执行
watchEffect时,不会 触发onInvalidate - 后续每次因响应式依赖变化而重新执行前,先运行
onInvalidate中的清理逻辑 - 组件销毁或手动
stop()时,也会触发一次onInvalidate
防抖搜索:用闭包 + onInvalidate 避免全局变量
推荐把定时器变量写在 watchEffect 回调内部,利用闭包保持引用,更安全、无污染:
- 每次执行都是独立作用域,
timer是局部变量,不会跨次干扰 -
onInvalidate能访问到当前闭包内的timer,清理精准 - 组件卸载后,
timer自然被垃圾回收,配合onInvalidate更稳妥
示例代码:
const keyword = ref('')watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log('发起请求:', keyword.value)
}, 500)
onInvalidate(() => clearTimeout(timer))
})
轮询定时器:用响应式开关统一管理生命周期
对于需要长期运行的 setInterval,应监听 isActive、intervalMs 等响应式字段,而非定时器本身:
- 定义
const isActive = ref(true)、const intervalMs = ref(3000) - 在
watchEffect内部先判断条件,再决定是否启动setInterval - 每次进入回调第一件事是清除旧定时器(即使未创建也要判空)
- 务必在
onInvalidate中再次清除并置空,形成闭环
这样可确保同一时刻最多只有一个有效定时器,避免堆积卡死。
flush 配置影响清理时机,按需选择
flush 控制副作用执行阶段,间接影响清理效果:
-
flush: 'pre':DOM 更新前执行,适合做前置清理(如暂停定时器) -
flush: 'post':DOM 更新后微任务中执行,适合依赖新 DOM 的操作(如动画、尺寸计算) -
flush: 'sync':同步执行,易引发渲染异常或循环,慎用
多数定时器清理场景选默认('pre')即可;若需操作更新后的界面元素,改用 'post'。










