深入解析 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. 如果处于开发模式,还会绑定调试钩子(onTrackonTrigger)。

七、总结

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

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

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


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

相关推荐
WooaiJava21 分钟前
AI 智能助手项目面试技术要点总结(前端部分)
javascript·大模型·html5
LYFlied25 分钟前
从 Vue 到 React,再到 React Native:资深前端开发者的平滑过渡指南
vue.js·react native·react.js
爱喝白开水a40 分钟前
前端AI自动化测试:brower-use调研让大模型帮你做网页交互与测试
前端·人工智能·大模型·prompt·交互·agent·rag
Never_Satisfied41 分钟前
在JavaScript / HTML中,关于querySelectorAll方法
开发语言·javascript·html
董世昌4141 分钟前
深度解析ES6 Set与Map:相同点、核心差异及实战选型
前端·javascript·es6
B站_计算机毕业设计之家1 小时前
豆瓣电影数据采集分析推荐系统 | Python Vue Flask框架 LSTM Echarts多技术融合开发 毕业设计源码 计算机
vue.js·python·机器学习·flask·echarts·lstm·推荐算法
WeiXiao_Hyy1 小时前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡2 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone2 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09012 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js