vue 3 computed

1. 类结构定义

class ComputedRefImpl<T> {
  // 核心状态
  private _value!: T         // 缓存的计算结果
  private _dirty = true      // 脏值标志
  public readonly effect: ReactiveEffect<T>

  // 是否支持设置(只读判断)
  public readonly __v_isReadonly: boolean

  constructor(
    getter: () => T,
    private readonly _setter: (newValue: T) => void,
    isReadonly: boolean
  ) {
    // 创建一个 effect 并配置调度器
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true     // 依赖变化时标记为需要重新计算
        trigger(toRaw(this), TriggerOpTypes.SET, 'value') // 触发依赖更新
      }
    })
    this.effect.computed = this // 关联 effect 和 computed 实例
    this.__v_isReadonly = isReadonly
  }

  get value(): T {
    // 关键:触发依赖收集和重新计算
    if (this._dirty) {
      this._value = this.effect.run()
      this._dirty = false
    }
    track(toRaw(this), TrackOpTypes.GET, 'value') // 收集当前依赖(如组件渲染函数)
    return this._value
  }

  set value(newValue: T) {
    this._setter(newValue) // 调用用户提供的 setter
  }
}

2. 关键实现细节

(1) 依赖追踪机制

  • 计算属性本身是被观察者:当其他代码(如模板)访问 computed.value 时,会触发 track 收集对 computedRef 的依赖。
  • 计算属性内部是观察者:通过 this.effect 订阅其 getter 中使用的响应式数据的变化。

(2) 调度器(Scheduler)

() => {
  if (!this._dirty) {
    this._dirty = true
    trigger(toRaw(this), TriggerOpTypes.SET, 'value')
  }
}
  • 当计算属性依赖的响应式数据变化时,调度器会被触发:
    • 标记 _dirty = true:表示下次访问 .value 时需要重新计算。
    • 调用 trigger:通知依赖此计算属性的副作用(如组件重新渲染)更新。

(3) 懒计算(Lazy Evaluation)

  • 计算属性的求值延后到 真正访问 .value 时进行:
    if (this._dirty) { // 只有当依赖变化时才会重新计算
      this._value = this.effect.run() // 执行 getter
      this._dirty = false
    }
    

3. 使用示例与流程

(1) 创建计算属性

const count = ref(0)
const double = computed(() => count.value * 2)

过程:

  1. 实例化 ComputedRefImpl
  2. 创建 ReactiveEffect 包裹 getter,并配置调度器函数

(2) 访问计算属性(首次)

console.log(double.value) // 0

流程:

  1. _dirtytrue → 执行 this.effect.run()
  2. 运行 () => count.value * 2 得到 0
  3. 收集 count.value 到此 effect 的依赖中
  4. _dirty 标记为 false
  5. 触发 track,将当前渲染函数等副作用订阅到 double 的变化

(3) 依赖更新

count.value++

流程:

  1. count 变更触发 doubleeffect 的调度器
  2. 标记 _dirty = true
  3. 调用 trigger 通知订阅者(如组件重新渲染)

(4) 再次访问

console.log(double.value) // 2 (触发重新计算)

4. 性能优化手段

  • 缓存机制:重复访问 .value 但依赖未变化时直接返回 _value
  • 批量更新:若多次修改依赖值,最终只重新计算一次。
  • 无效计算规避:若计算属性未被用到,其内部 effect 不会被激活。

与模板的联动

当模板中使用计算属性时:

<template>{{ double }}</template>
  • 组件渲染时会访问 double.value → 触发 getter
  • 模板成为计算属性的订阅者,当 count 变化时:
    1. 调度器标记 _dirty = true
    2. trigger 触发组件重新渲染
    3. 渲染时再次访问 double.value → 重新计算并缓存

源码中的实际调用

当调用 computed() 函数时:

function computed<T>(
  getter: () => T,
  debugOptions?: DebuggerOptions
): ComputedRef<T>
function computed<T>(
  options: WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions
): WritableComputedRef<T>
function computed<T>(
  getterOrOptions: any,
  debugOptions?: DebuggerOptions
) {
  let getter: () => T
  let setter: (v: T) => void

  // 处理 getter/setter 参数
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__ ? () => { /* 警告 */ } : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  // 创建 ComputedRefImpl 实例
  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}

总结

ComputedRefImpl 通过组合以下特性实现计算属性:

  1. 响应式 Effect 系统:追踪 getter 中的依赖。
  2. 脏值标记(_dirty):优化重新计算时机。
  3. 缓存机制(_value):避免重复计算。
  4. 调度器(Scheduler):控制依赖变更时的触发逻辑。
  5. 懒求值(Lazy):不访问时不会运行计算。

这种设计保证了计算属性既高效响应依赖变化,又避免不必要的性能消耗。

posted @   木燃不歇  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示