Computed 专用概览
仅聚焦
src/reactivity/ref/computed.ts中ComputedRefImpl的行为,说明 computed 自身如何维护派生状态。
设计目标
- 惰性求值:只有访问
.value时才运行 getter,避免无谓计算。 - 脏标记刷新:依赖变动后不会立刻执行 getter,而是把 computed 标记为脏,等下次读取时再重新求值。
- Ref 语义:computed 本质上是携带
dependencyBucket的 Ref,对外暴露统一的.value接口供 effect 或组件追踪。
核心结构
dependencyBucket:收集所有读取.value的外层 effect,脏标记触发时通过triggerEffects()通知这些订阅者。effect:内部ReactiveEffect只负责执行 getter,但不会自动收集下游依赖;它的调度器只做一件事——调用markDirty()。needsRecompute与cachedValue:组成最小缓存系统,当标记为脏时才清空缓存,下一次读取才会重新赋值。refFlag:实现Ref接口所需的标记位,isRef()可以据此识别ComputedRefImpl。
惰性读取流程
- 访问
.value时先执行trackEffect(dependencyBucket),让调用者与当前 computed 建立依赖。 - 如果
needsRecompute为真,运行内部 effect、缓存结果并把脏标记重置为假。 - 返回
cachedValue,确保多次读取仍是同一个引用,方便依赖比较。
只读与可写两种形态
- 直接传入 getter 时会创建只读 computed,setter 由
createReadonlySetter()抛出清晰的TypeError。 - 通过
WritableComputedOptions<T>({ get, set })传入时,可写 computed 会把.value = x委托给自定义 setter,自主决定如何同步派生状态。 - 两种形态共享同一套脏标记逻辑,因此即便是可写 computed,只要 getter 依赖的值变化仍会置脏并唤起订阅者。
依赖传播方式
- 上游依赖:getter 内部访问的任何响应式字段都会由内部 effect 自动追踪;变动后调度器调用
markDirty()。 - 下游依赖:
markDirty()只负责把needsRecompute置为真,并triggerEffects(dependencyBucket),让所有读取过.value的 effect 在下次调度时重新读取。 - 由于脏标记具备短路判断,连续多次触发在下一次读取前只会触发一次下游通知。
使用与排错提示
- 若发现 computed 永远不更新,优先确认 getter 中是否实际访问了响应式字段;只有被 track 到的依赖才会触发脏标记。
- 在模板或 effect 中频繁读取
.value不会重复计算,只要依赖未变,始终复用cachedValue。 - 可写 computed 的 setter 需要显式更新其依赖源,否则只会触发外部逻辑但不会自动刷新 getter 结果。