watchEffect 需手动清理定时器以避免内存泄漏和卡顿,通过 onInvalidate 在下次执行前清除上一个定时器;推荐用闭包局部变量而非 ref 存储 timer,结合 flush: 'post' 实现防抖轮询。
在 Vue 3 中,
本身不直接“监听定时器”,而是通过响应式依赖自动追踪副作用中的 reactive 数据或 ref 值。清理定时器的关键在于:**在副作用重新执行前,主动清除上一次启动的定时器**,避免多个定时器累积导致内存泄漏或页面卡顿。
为什么需要手动清理定时器?
如果你在
内部用
或
,而没做清理,每次响应式数据变化都会新建一个定时器——旧的还在跑,新的又来了。久而久之,几十甚至上百个定时器同时执行,CPU 占用飙升,页面明显卡顿。
watchEffect 的清理函数怎么用?
接收一个回调函数,该函数可选地接收一个
onInvalidate
参数(常简写为
或
),它会在本次副作用被覆盖(即下一次执行前)自动调用,适合用来清除定时器、取消请求、解绑事件等。
用法示例:
更安全的写法:用局部变量替代 ref 存储定时器
其实不需要把定时器存在
里。只要在
中能访问到当前定时器实例即可。推荐用闭包局部变量,更简洁、无副作用:
每次
执行,都会生成新的
变量
闭包捕获了本次的
,确保清理的是“自己启动的那个”
无需管理 ref 状态,逻辑更干净,也不怕重复清理
实战场景:搜索框防抖 + 定时轮询状态
比如用户输入关键词后,每 2 秒检查一次后台处理进度。既要防抖输入,又要定时轮询,且切换关键词时必须停掉旧轮询:
让副作用在组件更新后运行,更适合依赖最新 DOM 或 ref 值的场景
输入变化 → 上次
触发 → 清除旧定时器 → 启动新定时器
哪怕用户狂敲键盘,也只会有一个活跃轮询,彻底杜绝卡顿
watchEffectwatchEffectsetIntervalsetTimeoutwatchEffectonInvalidatecleanup
const timer = ref(null)
watchEffect((onInvalidate) => {
// 每次执行前,先清理上一次的定时器
onInvalidate(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
// 启动新定时器
timer.value = setInterval(() => {
console.log('定时任务执行中...')
}, 1000)
})
refonInvalidate
watchEffect((onInvalidate) => {
let timer = setInterval(() => {
console.log('执行中...')
}, 1000)
onInvalidate(() => {
clearInterval(timer)
})
})
watchEffecttimeronInvalidatetimer
const searchKey = ref('')
const status = ref('idle')
watchEffect((onInvalidate) => {
if (!searchKey.value.trim()) return
let count = 0
const timer = setInterval(() => {
count++
console.log(`正在轮询第 ${count} 次...`)
// 这里调用 API 获取 status
// fetchStatus(searchKey.value).then(res => status.value = res.status)
}, 2000)
onInvalidate(() => {
clearInterval(timer)
})
}, {
flush: 'post' // 确保 DOM 更新后执行,避免竞态
})
flush: 'post'onInvalidate