computed-源码解析
computed-源码解析
qf_luck在 Vue 3 中,computed 是响应式系统的核心模块之一,用于基于响应式数据派生出新的状态。其设计结合了依赖收集、缓存机制和异步调度,确保高效且精准的计算。以下从源码角度深入解析其实现原理。
一、核心类:ComputedRefImpl
computed 的核心逻辑封装在 ComputedRefImpl 类中,该类位于 @vue/reactivity 包的 computed.ts 文件。其核心结构如下:
1 | class ComputedRefImpl<T> { |
二、关键机制解析
1. 依赖收集与触发
- 依赖收集:当访问
computed.value时,track函数会将当前effect(通常是组件的渲染 effect)与computed实例关联。例如:1
2
3// 组件渲染时访问 computed
const count = computed(() => state.a + state.b);
// 渲染 effect 会被收集到 count.dep 中 - 触发更新:当依赖的响应式数据(如
state.a)变化时,trigger函数会遍历computed.dep中的所有 effect,并调用它们的scheduler:1
2
3// 依赖变化时触发
state.a = 2;
// computed 的 scheduler 会将 _dirty 设为 true
2. 缓存机制:脏标记(Dirty Flag)
- _dirty 的作用:
computed通过_dirty标志控制是否重新计算。首次访问时_dirty为true,执行effect.run()计算值并缓存到_value,同时将_dirty设为false。后续访问若_dirty为false,直接返回缓存值。 - 性能优化:只有当依赖变化时,
scheduler才会将_dirty设为true,避免不必要的计算。例如:1
2
3// 依赖未变化时多次访问
console.log(count.value); // 直接返回缓存值
console.log(count.value); // 同上
3. 副作用调度
computed 的 effect 会在依赖变化时通过 scheduler 触发更新,而非立即执行:
1 | this.effect = new ReactiveEffect(getter, () => { |
这种设计确保计算属性的更新与组件渲染同步,避免多次无效计算。
三、computed 函数的初始化
computed 函数负责创建 ComputedRefImpl 实例,并处理用户传入的 getter 和 setter:
1 | export function computed<T>( |
- 只读模式:传入函数时,
setter在开发环境抛出警告。 - 可写模式:传入对象时,使用用户定义的
setter处理赋值。
四、与 watch 的核心差异
| 特性 | computed | watch |
|---|---|---|
| 依赖处理 | 基于依赖自动收集,缓存结果 | 显式监听数据源,无缓存 |
| 执行时机 | 首次访问时计算,依赖变化后重新计算 | 默认立即执行,后续依赖变化时执行 |
| 使用场景 | 同步计算派生值 | 处理副作用(如 API 调用、日志) |
| 源码实现 | 基于 ComputedRefImpl 和 ReactiveEffect |
基于 watchEffect 和 effect |
五、典型场景与源码关联
1. 基础使用
1 | const count = ref(1); |
- 依赖收集:访问
count.value时,double.effect会被收集到count.dep中。 - 更新触发:
count.value++时,count.dep触发double.effect的scheduler,标记double._dirty = true。 - 缓存机制:再次访问
double.value时,若_dirty为true,重新计算并更新缓存。
2. 可写计算属性
1 | const count = ref(1); |
- setter 执行:
double.value = 4会调用用户定义的set,更新count.value为 2。 - 依赖更新:
count.value变化会触发double的重新计算。
六、性能优化与设计权衡
- 延迟计算:
effect.lazy = true确保只有在访问computed.value时才执行计算,避免初始化时的不必要开销。 - 异步调度:依赖变化时通过
scheduler触发更新,而非立即执行,与组件渲染周期同步。 - 多层依赖优化:若
computedA依赖computedB,computedB的更新会触发computedA的重新计算,但仅在必要时执行。
七、常见问题与源码解答
为何 computed 不会立即执行?
因为effect.lazy = true,首次访问value时才会调用effect.run()。如何处理循环依赖?
Vue 的响应式系统通过activeEffect栈和allowRecurse标志避免循环依赖导致的无限递归。可写 computed 的性能影响?
可写模式下,setter的执行会触发依赖更新,但与只读模式相比,性能差异可忽略不计。
总结
computed 的源码设计充分体现了 Vue 3 响应式系统的核心思想:按需计算、精准依赖追踪、高效更新。其通过 ComputedRefImpl 封装计算逻辑,结合 ReactiveEffect 和 Dep 实现依赖管理,最终在性能和易用性之间取得平衡。理解这些机制有助于更合理地使用 computed,并深入排查响应式系统的潜在问题。

