在 Vue 3 的响应式系统中,computed 是一个非常重要的功能,它用于创建基于依赖自动更新的计算属性。本文将通过分析源码,理解 computed 的底层实现逻辑,帮助你从源码层面掌握它的原理。


一、computed 的基本使用

在使用层面上,computed 有两种常见用法:

1. 只读计算属性

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 输出 2
plusOne.value++ // 报错,因只读

2. 可写计算属性

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => { count.value = val - 1 }
})

plusOne.value = 3
console.log(count.value) // 输出 2

这两种形式在底层源码中都会通过一个统一的类 ComputedRefImpl 来实现。


二、核心类:ComputedRefImpl

ComputedRefImpl 是 Vue 3 中计算属性的核心实现类,它实现了响应式依赖追踪和懒执行机制。

1. 成员属性解析

export class ComputedRefImpl<T = any> implements Subscriber {
  _value: any = undefined          // 缓存计算后的值
  dep: Dep = new Dep(this)         // 依赖收集容器
  readonly __v_isRef = true        // 标记为 ref 类型
  readonly __v_isReadonly: boolean // 是否只读
  deps?: Link                      // 依赖链表(订阅者)
  flags: EffectFlags = EffectFlags.DIRTY // 标志位,初始为脏值
  globalVersion: number = globalVersion - 1 // 全局版本号
  isSSR: boolean                   // 是否在服务端渲染环境中
  next?: Subscriber = undefined    // 下一个订阅者

  // 调试钩子
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: ComputedGetter<T>,
    private readonly setter: ComputedSetter<T> | undefined,
    isSSR: boolean,
  ) {
    this[ReactiveFlags.IS_READONLY] = !setter
    this.isSSR = isSSR
  }
}

关键点:

  • flags:使用 EffectFlags 管理状态,如 DIRTY 表示需要重新计算。
  • dep:内部维护依赖列表,供其他响应式对象追踪。
  • _value:缓存上次计算的值,实现懒计算。
  • __v_isReadonly:若没有传入 setter,则为只读计算属性。

三、value 的访问逻辑

get value(): T {
  const link = this.dep.track() // 依赖追踪
  refreshComputed(this)         // 若脏则重新计算
  if (link) {
    link.version = this.dep.version
  }
  return this._value
}

这里是 computed 的核心机制:

  1. 依赖追踪:通过 dep.track() 让当前计算属性被其他响应式数据订阅。
  2. 惰性求值:只有在访问 .value 时,才会执行 getter 重新计算。
  3. 版本同步link.version 确保依赖的版本号一致,以判断是否需要重新计算。

四、value 的设置逻辑

set value(newValue) {
  if (this.setter) {
    this.setter(newValue)
  } else if (__DEV__) {
    warn('Write operation failed: computed value is readonly')
  }
}
  • 若定义了 setter,则允许外部修改计算属性值;
  • 否则在开发环境中发出警告,提示为只读属性。

五、依赖更新与通知机制

当依赖的响应式数据发生变化时,notify() 会被调用:

notify(): true | void {
  this.flags |= EffectFlags.DIRTY // 标记为脏值
  if (!(this.flags & EffectFlags.NOTIFIED) && activeSub !== this) {
    batch(this, true) // 批量更新依赖
    return true
  }
}

这里的关键是:

  • 标记为 “脏”,下次访问时会重新计算;
  • 通过 batch 批量更新,避免重复通知。

六、computed 函数入口

外层的 computed 函数是一个工厂方法:

export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false,
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T> | undefined

  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  const cRef = new ComputedRefImpl(getter, setter, isSSR)

  if (__DEV__ && debugOptions && !isSSR) {
    cRef.onTrack = debugOptions.onTrack
    cRef.onTrigger = debugOptions.onTrigger
  }

  return cRef as any
}

这里做了两件事:

  1. 根据参数判断是只读还是可写计算属性;
  2. 创建 ComputedRefImpl 实例;
  3. 如果处于开发模式,还会绑定调试钩子(onTrackonTrigger)。

七、总结

Vue 3 中的 computed 实现基于以下关键机制:

机制作用
惰性求值 (Lazy Evaluation)仅在访问时计算结果
缓存结果 (Caching)若依赖未变则返回缓存值
依赖追踪 (Dependency Tracking)自动感知依赖变化
脏标记 (Dirty Flag)控制何时重新计算
批量更新 (Batching)提高性能,减少重复通知

从源码可以看到,computed 实际上是一个特殊的 ref,但拥有更多的依赖追踪与缓存机制,是 Vue 响应式系统中非常精妙的一环。


本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]