watchEffect 通过函数实际执行时的读取行为动态追踪依赖,访问响应式数据触发 getter 拦截并自动记录依赖,条件分支未执行部分不被追踪,每次执行后用新依赖完全替换旧依赖,天然支持动态依赖和深层字段。

watchEffect 的依赖追踪不是靠“扫描代码”或“静态分析”,而是靠函数**实际执行时的读取行为**来动态捕获。它不关心你写了什么,只关心你**真正读了什么**。
依赖是在执行中实时记下的
你传给 watchEffect 的函数,Vue 会立刻运行一次。这一跑,就触发了响应式数据的 getter:
- 只要访问 ref.value、reactive 对象的属性(如 user.name)、computed 的值,Proxy 的 get 拦截器就会被触发
- 此时 Vue 把当前正在运行的这个副作用函数,悄悄记进对应响应式数据的“依赖列表”里
- 比如函数里写了
searchTerm.value和user.profile.city,那这两个就是本次收集到的依赖
条件分支决定哪些依赖会被追踪
依赖收集是路径敏感的——没执行到的代码,不会建立依赖:
-
if (showDetails) { user.address.zip }:只有 showDetails 为 true 时,user.address.zip才会被读取、被追踪 -
if (false) { count.value }:这行根本不会执行,count 就不会成为依赖 - 这意味着依赖集合每次执行都可能不同,Vue 会用新依赖完全替换旧依赖
变化时重跑,依赖自动更新
当某个被追踪的值变了(setter 被触发),Vue 就知道该通知谁:
- 找到它的所有依赖函数,重新调用 watchEffect 回调
- 新执行过程中,再次走一遍“读取 → 拦截 → 记录”的流程
- 上一轮没读的,这次不加;这次新读的,自动加入;旧的没再读的,自然被丢弃
- 所以它天然支持动态依赖、深层字段(只要你显式读了
obj.a.b,就不会漏)
不需要手动列依赖,是因为它不等你指派
watchEffect 的核心逻辑其实是:“你先跑,我边看边记”。和 watch 的“你告诉我盯谁,我再去盯”完全不同:
- watch 需要你写
watch(() => obj.x, ...)或watch([a, b], ...) - watchEffect 只需要
watchEffect(() => { /* 你自由读 */ }) - 它省掉的是配置层,让副作用逻辑更贴近真实的数据使用流


















