1.Vue 3 中watchEffect的onInvalidate是一个用于‌清理副作用‌的关键机制。它允许你在副作用函数重新执行之前或者在监听器停止时执行清理操作如清除定时器、取消网络请求、解绑事件监听等从而有效防止内存泄漏和逻辑错误。1. 核心作用与触发时机onInvalidate接收一个回调函数作为参数该回调会在以下两种情况下被调用‌副作用即将重新执行前‌当watchEffect追踪的响应式依赖发生变化导致副作用函数需要再次运行时Vue 会先调用onInvalidate中的清理函数然后再执行新的副作用逻辑。‌监听器停止时‌当组件卸载或手动调用stop()函数停止监听时。‌注意‌在watchEffect首次执行时不会触发onInvalidate因为此时没有“上一次”的副作用需要清理。2. 典型应用场景A. 清理定时器防止堆积如果在watchEffect中使用了setInterval或setTimeout每次依赖变化都会创建一个新的定时器。如果不清理旧的定时器会继续运行导致多个定时器同时存在引发性能问题。import { ref, watchEffect } from vue const count ref(0) watchEffect((onInvalidate) { // 1. 启动定时器 const timer setInterval(() { console.log(Timer running..., count.value) }, 1000) // 2. 注册清理函数 // 当下次 count 变化或组件卸载时清除当前的 timer onInvalidate(() { clearInterval(timer) console.log(Timer cleared) }) })B. 取消过期的异步请求防止竞态条件在搜索框等场景中用户快速输入会导致多次发起 API 请求。由于网络延迟后发出的请求可能比先发出的请求更早返回导致页面显示旧数据。使用onInvalidate可以标记之前的请求为“已废弃”忽略其返回结果。import { ref, watchEffect } from vue const keyword ref() const results ref([]) watchEffect(async (onInvalidate) { if (!keyword.value) return // 标记当前请求是否有效 let isCancelled false // 注册清理函数如果副作用重新执行将上一个请求标记为取消 onInvalidate(() { isCancelled true }) try { const res await fetch(/api/search?q${keyword.value}) const data await res.json() // 只有当请求未被取消时才更新数据 if (!isCancelled) { results.value data } } catch (error) { if (!isCancelled) { console.error(error) } } })C. 解绑事件监听避免在组件生命周期内重复绑定同一事件造成内存泄漏或多次触发。watchEffect((onInvalidate) { const handler () console.log(Window resized) window.addEventListener(resize, handler) // 清理移除事件监听 onInvalidate(() { window.removeEventListener(resize, handler) }) })3. 最佳实践与注意事项‌推荐使用局部变量而非 Ref 存储资源ID‌在清理定时器或请求时建议直接在watchEffect内部使用局部变量如let timer来存储资源 ID而不是将其存储在ref中。这样利用闭包特性onInvalidate能准确捕获当前这次执行所创建的資源代码更简洁且不易出错。‌执行顺序‌当依赖变化时执行顺序为触发onTrigger调试用执行上一次的onInvalidate清理函数执行当前的副作用函数‌与flush选项配合‌watchEffect默认在组件更新前执行flush: pre。如果你需要在 DOM 更新后执行副作用例如获取元素尺寸可以配置flush: post。无论flush设置为何值onInvalidate都会在下次副作用执行前被调用。watchEffect((onInvalidate) { // ... 副作用逻辑 }, { flush: post // 在 DOM 更新后执行 })‌不要用于获取旧值‌watchEffect无法直接获取数据的“旧值”。如果你需要对比新旧值例如判断数据是从 A 变到 B 还是从 B 变到 A应该使用watchAPI而不是watchEffect。