深入解析 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 响应式系统中非常精妙的一环。


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

相关推荐
码事漫谈3 分钟前
大模型输出的“隐性结构塌缩”问题及对策
前端·后端
这儿有一堆花29 分钟前
前端三件套真的落后了吗?揭开现代 Web 开发的底层逻辑
前端·javascript·css·html5
.Cnn1 小时前
JavaScript 前端基础笔记(网页交互核心)
前端·javascript·笔记·交互
醉酒的李白、1 小时前
Vue3 组件通信本质:Props 下发,Emits 回传
前端·javascript·vue.js
anOnion1 小时前
构建无障碍组件之Window Splitter Pattern
前端·html·交互设计
小眼哥2 小时前
SpringBoot整合Vue代码生成exe运行程序以及windows安装包
vue.js·windows·spring boot
NotFound4862 小时前
实战分享Python爬虫,如何实现高效解析 Web of Science 文献数据并导出 CSV
前端·爬虫·python
徐小夕2 小时前
PDF无限制预览!Jit-Viewer V1.5.0开源文档预览神器正式发布
前端·vue.js·github
WangJunXiang62 小时前
Haproxy搭建Web群集
前端
吴声子夜歌2 小时前
Vue.js——自定义指令
前端·vue.js·flutter