Vue3源码分析(三)- computed 原理

33 阅读2分钟

computed 的源码很大程度是依赖于 effect 的实现,本质就是一个 effect。以下是本文的主要讲解内容:

  • computed 自动更新机制
  • computed 缓存
  • computed 的源码实现

自动更新机制

当计算属性所依赖的响应式数据发生变化时,计算属性会自动重新计算并更新其值,从而实现了响应式触发变化的效果

  • 依赖追踪:在计算属性的 getter 函数执行过程中,会自动收集依赖的响应式数据并建立依赖关系

  • 自动更新:由于计算属性和响应式数据之间的依赖关系进行了追踪,会在依赖数据发生变化时,自动通知相关的计算属性进行更新

缓存

通过缓存计算结果并根据依赖项的变化情况来决定是否重新计算的机制来实现,在 getter 函数中通过 _cacheable 和 effect.dirty 标志来判断缓存策略

  • _cacheable:值为 false 表示计算引用不允许缓存,值为 true 反之
  • effect.dirty:值为 true 表示计算引用处于脏状态,值为 false 反之

当计算引用允许缓且处于脏状态,同时新旧值发生了变化,就需要触发依赖更新进行重新计算

源码实现

computed 函数

主要做了两件事情:

  • 定义 getter、setter
  • 创建 ComputedRefImpl 实例并返回
// packages/reactivity/src/computed.ts

export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
  // 只保留核心代码
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  const onlyGetter = isFunction(getterOrOptions) // 判断参数是否为函数
  if (onlyGetter) {
  // 如果是function,则computed是只读的
    getter = getterOrOptions
    setter = __DEV__
      ? () => {
          warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter)

  return cRef as any
}

ComputedRefImpl 类

// packages/reactivity/src/computed.ts

export class ComputedRefImpl<T> {
  public dep?: Dep = undefined // 引入当前computed的effect

  private _value!: T // 私有属性,缓存计算后的结果
  public readonly effect: ReactiveEffect<T> // 只读,用来存放 ReactiveEffect 函数

  public readonly __v_isRef = true // 只读,标识是否为 ref
  public readonly [ReactiveFlags.IS_READONLY]: boolean = false // 标记是否可读

  public _cacheable: boolean // 表示是否有缓存
  
  constructor(
    private getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean,
  ) {
  // 创建effect实例对象,将当前的getter作为回调函数
    this.effect = new ReactiveEffect(
      () => getter(this._value),
      () =>
        triggerRefValue(
          this,
          this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect // 判断effect的脏状态级别
            ? DirtyLevels.MaybeDirty_ComputedSideEffect // 表示可能会脏化,由计算引用的副作用触发
            : DirtyLevels.MaybeDirty, // 可能会脏化,但原因明确
        ),
    )
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
  
  get value() {
    const self = toRaw(this)
    if (
      (!self._cacheable || self.effect.dirty) && // 表示不允许缓存且处于脏状态
      hasChanged(self._value, (self._value = self.effect.run()!)) // 通过 Object.is() 判断新旧值是否相等
    ) {
      triggerRefValue(self, DirtyLevels.Dirty) // 触发依赖变更
    }
    trackRefValue(self) // 收集依赖
    if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
      if (__DEV__ && (__TEST__ || this._warnRecursive)) {
        warn(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter)
      }
      triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
    }
    return self._value
  }
 }