watcheffect 的 oncleanup 是防止竞态、内存泄漏和状态错乱的必要机制,本质是清理“已过期但未完成”的副作用,必须在同步阶段调用,用于取消请求、清除定时器、移除事件监听等。

watchEffect 的 onCleanup 是处理异步副作用的核心机制,不是可有可无的附加功能,而是防止竞态、内存泄漏和状态错乱的必要手段。
它本质是一个“清理注册器”:你把要取消的操作(比如 abort 请求、清除定时器)写进 onCleanup(() => { ... }),Vue 就会在上一轮副作用被覆盖前或组件卸载时自动调用它——只清理“还没完成却已过期”的那部分逻辑。
onCleanup 必须在同步阶段调用
onCleanup 只能在 watchEffect 回调函数的同步执行部分里调用。一旦进入 await 或 setTimeout 等异步上下文,Vue 已经丢失当前响应式副作用的上下文,再调就直接报错。
✅ 正确:
watchEffect((onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort()) // 同步位置,立刻注册
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(data => result.value = data)
})
❌ 错误:
watchEffect(async (onCleanup) => {
await someAsyncSetup() // 此时 activeEffect 已结束
onCleanup(() => ...) // ❌ 报错:no active effect found
})
常见清理场景与写法
-
取消网络请求(推荐用
AbortController)- 每次
watchEffect触发都新建AbortController - 把
signal传给fetch或axios -
onCleanup中调controller.abort() - 响应返回后检查
signal.aborted或加cancelled标记,避免更新过期状态
- 每次
-
清除定时器
let timer watchEffect((onCleanup) => { timer = setTimeout(() => { /* do something */ }, 1000) onCleanup(() => clearTimeout(timer)) }) -
移除事件监听器
watchEffect((onCleanup) => { const handler = () => console.log('resize') window.addEventListener('resize', handler) onCleanup(() => window.removeEventListener('resize', handler)) }) -
销毁第三方实例
let map watchEffect((onCleanup) => { map = new MapboxMap(...) onCleanup(() => map.remove()) // 或 map.destroy() })
它不等于“每次执行完都清理”
onCleanup 不是 effect 执行结束后的“收尾钩子”,而是上一次未完成操作的终结者。
比如用户连续输入搜索词:
- 第一次触发 → 发起请求 A,注册 cleanup A
- 输入变化 → 触发第二次 → 先执行 cleanup A(abort A),再发起请求 B
- 请求 A 若此时返回,应忽略;请求 B 返回才更新 UI
这个“旧退新进”的节奏,靠的就是 onCleanup 的精准触发时机。
主动停止 watchEffect
如果需要手动终止监听(比如页面切换时提前清理),可捕获 watchEffect 的返回值:
const stop = watchEffect(() => { /* ... */ })
// 后续调用
stop()
调用 stop() 会立即触发已注册的 onCleanup,并停止后续响应式追踪。组件卸载时 Vue 也会自动调用它。
不复杂但容易忽略。











