watcheffect 的自动追踪本质是同步执行时对实际读取的响应式值进行依赖收集,异步读取不参与,每次重执行都会重新收集并自动清除旧依赖,且仅追踪显式访问到的深层属性。

watchEffect 的“自动追踪”,本质是 Vue 3 响应式系统在函数执行瞬间完成的一次“现场快照”——它只盯住你**此刻真正读取了的响应式值**,而不是你声明了什么变量或写了什么路径。
依赖收集只发生在同步执行阶段
当你写:
watchEffect(() => {if (show.value) {
console.log(user.profile.city);
}
});
Vue 会在首次运行时: - 先执行整个函数体; - 仅当 show.value 为 true 时,user.profile.city 才被读取 → 这个属性才被 track(记录为依赖); - 如果 show.value 是 false,user.profile.city 根本没被访问,就不会进依赖列表,后续改它也不会触发重跑。
也就是说,分支、循环、条件语句里的响应式读取,只有实际执行到那行代码,才算数。
异步代码不参与初次依赖收集
下面这段不会追踪 count.value 的变化:
watchEffect(async () => {await new Promise(r => setTimeout(r, 100));
console.log(count.value); // 这行在异步回调里,不在初始同步执行流中
});
原因很直接: - 初始同步执行只走到 await,然后暂停; - count.value 是在后续微任务中读的,此时 track 阶段早已结束; - 所以这个 count.value 不会被监听,改了也不会重跑函数。
要让它生效,得把依赖提前拉到同步位置:
const current = count.value; // 同步读取,被追踪
setTimeout(() => console.log(current), 100);
});
每次重执行都会重新收集,旧依赖自动失效
watchEffect 不是“一次收集、永久有效”。每次回调重新运行,都是一次全新依赖采集:
- 上一轮读了 a.value 和 b.value,这一轮只读 c.value → a 和 b 的依赖关系自动解除
- 如果这次执行进了新的 if 分支,读到了之前没碰过的 user.avatar,那 avatar 就加入新依赖集
- 不需要手动清空或更新依赖数组,Vue 自动完成“旧弃新立”
这种动态性让逻辑更贴近真实数据流向:代码怎么走,依赖就怎么建。
浅层追踪对 reactive 对象的影响
对于 reactive({ profile: { city: 'Beijing' } }):
- 读取 user.profile.city → 会追踪 city 属性(深层变更可触发)
- 只读 user.profile → 只追踪 profile 对象本身(替换 profile 整体才会触发)
- 不读任何属性(比如只 console.log(user))→ 没有依赖,只执行一次
关键点在于:必须显式访问到最终字段,Vue 才知道该盯谁。











