深入解析 Vue 3 源码:computed 的底层实现原理

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


一、computed 的基本使用

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

1. 只读计算属性

scss 复制代码
const count = ref(1)
const plusOne = computed(() => count.value + 1)

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

2. 可写计算属性

ini 复制代码
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. 成员属性解析

typescript 复制代码
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 的访问逻辑

kotlin 复制代码
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 的设置逻辑

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

五、依赖更新与通知机制

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

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

这里的关键是:

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

六、computed 函数入口

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

ini 复制代码
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. 如果处于开发模式,还会绑定调试钩子(onTrack、onTrigger)。

七、总结

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

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

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


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

相关推荐
陈随易18 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean20 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn21 小时前
性能监控
前端
Highcharts.js21 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn21 小时前
构建工具
前端
FRing031 天前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷1 天前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen1 天前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒1 天前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫1 天前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库