依赖收集发生在读取响应式字段时,需同时满足:访问响应式数据、处于活跃effect中、触发get拦截;vue 3通过targetmap-depsmap-dep三层结构记录依赖关系,修改时通知对应effect执行并重新收集。

依赖收集不是 Vue 主动“扫描”谁用了数据,而是在你读取响应式字段的瞬间,悄悄记下“此刻正在执行的逻辑是谁”——它只发生在读取时,且仅限于有活跃副作用(如组件渲染、watchEffect)的上下文中。
依赖收集发生的三个前提
它不会无条件触发,必须同时满足:
-
访问的是响应式数据:比如
ref.value、reactive.obj.name;普通对象或未经过reactive/ref处理的数据不参与 -
当前处于活跃的 effect 中:组件 render 函数、
computed、watchEffect或手动调用的effect()正在执行 -
该访问触发了 get 拦截:直接写
obj.prop会触发;但Object.keys(obj)、JSON.stringify(obj)或for...in(未用ownKeys拦截时)通常绕过 getter,不收集
底层怎么记“谁在看谁”?
Vue 3 用三层结构维护这个关系:
-
targetMap:WeakMap,键是响应式对象,值是它的
depsMap -
depsMap:Map,键是属性名(如
'count'),值是该属性的依赖集合dep - dep:Set,存所有依赖这个属性的 effect 函数(比如组件的 render 函数)
当你写 state.count,Proxy 的 get 拦截器就查 targetMap.get(state).get('count'),再把当前 activeEffect 加进去——整个过程毫秒级,但绝不冗余。
为什么有些读取没被收集?
常见“看似用了却没收集”的情况:
- v-if="false" 里的表达式:条件不成立,render 函数没执行到那行,自然没触发 getter
-
console.log(state.xxx) 在 setup 外部:没有 activeEffect,
activeEffect为 null,跳过收集 -
shallowReactive 对象的深层属性:比如
shallowReactive({ user: { name: 'a' } }),读data.user.name不触发收集,因为user没被代理 - 数组索引赋值(如 arr[5] = x):Vue 3 的 Proxy 能拦截,但 Vue 2 的 defineProperty 无法监听,所以旧项目中这种写法不会触发更新
收集完之后,数据变了怎么通知?
收集只是建链路,真正起作用在修改时:
- 你改
state.count = 10,set拦截器查到targetMap.get(state).get('count') - 遍历它里面的每个 effect,把它们推入异步更新队列(microtask)
- 同一 tick 内重复修改
count,只调度一次 effect,避免重绘浪费 - effect 重新执行 → 触发新的依赖收集(可能新增、删减依赖),形成闭环










