computed 的动态依赖收集是懒执行、按需更新的,每次求值时重新追踪实际访问的响应式数据,分支切换会覆盖式重收集依赖,未执行分支不收集任何依赖。

Computed 的动态依赖收集,核心在于“响应式追踪只发生在实际取值时”,且依赖关系会随执行路径实时变化——不是静态写死的,而是每次求值都重新捕获当前分支中真正访问到的响应式数据。
依赖是懒收集、按需更新的
computed 函数本身不立刻执行,只有在被其他响应式副作用(比如渲染、另一个 computed 或 watch)读取其值时,才会触发求值。此时内部函数运行,Vue 才会实时记录这次执行中 实际读取了哪些 ref 或 reactive 属性。没走到的分支(如 if 里的代码块、未执行的三元表达式分支)不会收集任何依赖。
例如:
const count = ref(0)
const mode = ref('a')
<p>const result = computed(() => {
if (mode.value === 'a') {
return count.value * 2 // 只有 mode==='a' 时,才依赖 count
} else {
return count.value + 10 // 只有 mode!=='a' 时,才依赖 count(但逻辑不同)
}
})</p>当 mode.value 从 'a' 切换到 'b',result 重新求值:上一次收集的依赖(count)仍存在,但本次执行走的是 else 分支,依然访问了 count.value —— 所以依赖没变;但如果 else 分支里访问的是另一个 ref(比如 other.value),那依赖就会切换过去。
分支切换会引发依赖的“覆盖式重收集”
每次 computed 重新求值,都会丢弃上一次的依赖集,重新开始追踪。这意味着:
- 如果新分支访问了新响应式变量,就新增对应依赖
- 如果新分支跳过了某个变量,即使它曾被依赖过,这次也不再关联
- 如果两个分支都访问同一个 ref,那无论怎么切换,该 ref 始终在依赖列表中
这正是“动态”的体现:依赖图不是构建一次就固定,而是随状态流持续演进。
注意嵌套与条件访问带来的陷阱
有些写法看似有分支,实则依赖不会切换:
-
foo.value && bar.value:若 foo.value 为 falsy,bar 不会被读取,也就不会收集 bar 的依赖 -
arr[index]?.name:若 index 越界或 arr[index] 为 null/undefined,name 不触发读取,不收集 name 依赖 - 深层属性访问如
obj?.user?.profile?.age:只要中间任意一环为 null/undefined,后续属性都不参与依赖收集
这些安全访问操作让依赖收集更精确,但也容易让人误以为“整个链路都被监听”,其实只是“走到哪,记到哪”。
调试依赖的小技巧
想确认某次求值到底收集了哪些依赖,可以临时在 computed 回调里加一句:
console.log('当前依赖:', [...effect._deps].map(d => d.key)) // ⚠️ 非公开 API,仅用于调试理解更稳妥的方式是配合 Vue Devtools 查看 computed 的“Dependencies”面板,它会如实显示每次求值后活跃的响应式引用。











