watcheffect 的自动收集依赖是运行时动态追踪:首次执行时通过 proxy 拦截 get 操作,仅对实际读取的响应式数据(如 ref.value、reactive 属性)调用 track 注册依赖;未访问、非响应式或条件分支未执行的属性均不被收集,依赖关系随执行路径动态更新。

可以这么理解:watchEffect 的“自动收集”,本质是 Vue 在它内部函数第一次执行时,悄悄记下所有被读取过的响应式数据(比如 ref.value、reactive 对象的属性),然后把这些数据和这个函数“绑在一起”。之后只要其中任何一个变了,函数就重新跑一遍。
它不是靠语法分析,而是靠运行时拦截
Vue3 用 Proxy 拦截了对响应式对象的读取操作(get)。当你在 watchEffect 回调里写 count.value 或 user.name,这些读取动作会触发 Proxy 的 get 拦截器,里面就会调用 track(),把当前正在执行的 watchEffect 函数注册为该属性的依赖。
- 没访问的,就不会被 track,也就不会响应变化
- 访问了但不是响应式数据(比如普通变量、常量、计算后的字符串),不会被收集
- 如果某次执行中条件分支没走到某句读取,那这次就不会收集那个属性——下次走到时才会加入依赖
一个例子看动态性
假设你写:
watchEffect(() => {
console.log(state.count)
if (showUser.value) {
console.log(user.name, user.age)
}
})
初始时 showUser.value 是 false,那么只有 state.count 被收集;等它变成 true 后,下一次执行时 user.name 和 user.age 才会被 track 进来——依赖关系是随着代码实际执行路径动态更新的。
和 watch 的显式声明对比
watch 要你提前写死监听谁:
watch([() => state.count, () => user.name], ...) // 即使 user.name 这次根本没用上,也一直被监听
而 watchEffect 只关心“这次真正读了什么”,更贴近实际逻辑流,也更少误监听。
注意边界:哪些算“访问”?
以下都会被收集:
- count.value
- obj.name(obj 是 reactive)
- arr[0](arr 是 reactive 数组)
- computedRef.value
以下不会被收集:
- JSON.stringify(obj)(只是序列化,没触发 getter)
- Object.keys(obj)(Proxy 不拦截 keys,除非用 toRaw 或 markRaw)
- 普通 const 变量或字面量











