watchEffect侦听器:自动追踪响应式依赖
watchEffect在定义时立即执行,通过运行时读取响应式属性自动追踪依赖,仅跟踪实际用到的部分,节省性能。条件分支中的依赖可能动态变化。异步场景需用onInvalidate清理竞态。如需新旧值对比或深层监听,应改用watch。
先回答一个很实际的问题:watchEffect 到底是怎么“自动”追踪依赖的?
简单说,它并不需要你手动列出一堆变量名,也不需要你配置 immediate: true——因为它在定义时就会立刻执行一次,同时悄悄记录下函数内部真正访问到的每一个响应式属性。之后只要这些属性发生变化,它就会自动重新执行。整个过程靠的是 Vue 内部的 getter/setter 拦截,对开发者来说几乎是透明的。

不过这里有个关键:它并不是扫描变量名,而是真正运行时“读”到了什么。比如你写了 count.value 和 user.profile.name,它就只盯着这两个;如果用了条件分支 if (show) { data.list },那只有当 show 为 true 时,data.list 才会被加入依赖。普通变量、常量、非响应式对象的属性,哪怕在同一行出现,也不会被追踪。另外注意,赋值操作(比如 count.value = 5)本身不会触发依赖收集——只有读取才收集。
watchEffect 只追踪真正被用到的响应式值
回到刚才的逻辑,这种方法的好处是节省性能:你只关心用到的部分,没用到的改动一律不触发回调。但也意味着你要小心条件分支中的依赖可能“动态”出现或消失。
watchEffect 首次运行即生效,后续变化自动响应
定义 watchEffect 的那一刻,函数会同步执行一次,这相当于内置了 immediate: true。所以非常适合初始化请求、设置定时器、同步 DOM 状态这类场景。组件卸载时,这个侦听器会自动停止,不需要你在 onUnmounted 里手动清理。如果确实想跳过首次执行,一般得借助 onInvalidate 配合逻辑控制,不过实际需求很少。
watchEffect 异步任务要防竞态,用 onInvalidate 清理上一轮
真正需要警惕的是异步场景,比如输入搜索、滚动监听这些高频触发的情况。很容易出现“旧请求还没回来,新请求又发了”,导致页面展示错误的数据。这时 onInvalidate 回调就派上用场了——它会在下一次执行前自动调用上一次注册的清理函数。你可以在函数开头调用 onInvalidate(() => clearTimeout(timer)) 来取消定时器,或者在发起 fetch 时用 AbortController,调用 abort() 取消旧请求。有一个细节:清理函数必须在异步操作启动前注册,否则可能无法正确清理正在运行的任务。
什么时候该换用 watch?
watchEffect 虽简洁,但不是万能。以下几种情况换 watch 会更合适:
- 需要对比新旧值,比如只在值从空变为非空时触发;
- 要监听深层嵌套对象的任意字段变化(需要
deep: true),watchEffect不支持深层监听; - 依赖来源过于分散,或者计算属性的 getter 里包含异常判断逻辑,用
watch更容易控制; - 只是想单纯地把响应式数据加工成另一个响应式值,那么优先考虑
computed,语义更清晰。
说白了,watchEffect 是为“闭着眼睛自动跑”的场景设计的,而 watch 给了你更多精细控制的手柄。二者配合使用,才能写出既简洁又可靠的响应式逻辑。
