Vue3源码-computed&deferredComputed

201 阅读3分钟
  • : 只用fullName去值,才会计算传入computed的回调。
  • 先收集fullName的effect,再收集firstName的effect。

测试用例:

    const {reactive, effect} = VueReactivity
    const state = reactive({ firstName: 'l'})
    let fullName = computed(()=>{
        return state.firstName
    })
    effect(()=>{
        app.innerHTML = fullName.value;
    })
    setTimeout(()=>{
        state.firstName = "路"
    })

computed

export function computed<T>(getterOrOptions) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter)
  return cRef as any
}
export class ComputedRefImpl<T> {
  public dep?: Dep = undefined
  private _value!: T
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true
  public readonly [ReactiveFlags.IS_READONLY]: boolean = false
  public _dirty = true
  public _cacheable: boolean
  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean
  ) {
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    })
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
  get value() {
    const self = this
    // 收集fullName 的effect。
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      // 就是去计算computed插入的回调的,收集firstName的effect
      self._value = self.effect.run()!
    }
    return self._value
  }
  set value(newValue: T) {
    this._setter(newValue)
  }
}
  • state.firstName = "路",更改firstName 会触发对应的effect,执行triggerEffects,firstName对应的effect的effect.computed存在,执行相应的triggerEffect。
  • 此时effect.scheduler 存在,执行scheduler,会执行 triggerRefValue(this)即执行fullName对应的effect的trigger.执行effect.run().去取fullName.value的值。
  • 之后触发 fullName的get函数,执行trackRefValue(self),对fullName的这个effect进行收集。
  • 再去执行self._value = self.effect.run()!,去执行firstName对应的effect.run,截止执行this.fn()即computed传入的回调函数,取firstName的值,对此effect进行收集,并将函数计算的新值,传入self._value,此时就完成了更新。
export function triggerEffects(dep) {
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      // 先更新计算属性
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    // 再更新effect
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}
function triggerEffect(effect: ReactiveEffect) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

测试用例

        const tick = Promise.resolve()
        const src = ref(0)
        const c1 = deferredComputed(() => {
            console.log("c1")
            return src.value % 2
        })
        const c2 = computed(() => {
            console.log("c2")
            return c1.value + 1
        })

        effect(() => {
            console.log('effect')
            console.log(c2.value)
        })
        src.value = 1
        await tick
        // 开始执行 传入queue中的回调
        // 打印结果 effect c2 c1 1 c1 effect c2 2
  constructor(getter: ComputedGetter<T>) {
    let compareTarget: any
    let hasCompareTarget = false
    let scheduled = false
    this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => {
      // dep中存放的是使用fullName的activeEfect.
      // 有effect使用了fullName才执行,没有就不管了。
      if (this.dep) {
        if (computedTrigger) {
          compareTarget = this._value
          hasCompareTarget = true
        } else if (!scheduled) {
          const valueToCompare = hasCompareTarget ? compareTarget : this._value
          scheduled = true
          hasCompareTarget = false
          // 传入queue中的回调,promise.resolve()后执行回调。
          scheduler(() => {
            // this._get()会将this._dirty重置成false,
            // 在triggerRefValue获取fullName的值时,
            // 执行toRaw(this)._get(),此时firstName已经收集过依赖了,
            // 直接放回this._vlaue的值。
            if (this.effect.active && this._get() !== valueToCompare) {
              triggerRefValue(this)
            }
            scheduled = false
          })
        }

      }
      // 修改过了就是ture
      this._dirty = true
    })
    this.effect.computed = this as any
  }
  • src.value = 1,执行对应的trigger-->triggerEffects---> src.value收集的effect是DeferredComputedRefImpl.effect,此effect包含computed属性,---> triggerEffect---->此effect有scheduler函数,执行scheduler函数。将回调函数传入queue中,就执行完了。
  • 等到await tick执行完后,执行传入到queue中的回到函数。
  • 执行this._get(),执行this.effect.run()---->执行传入deferredComputed的回调,在控制台打印c1,取值src.value---->使src.value收集这个deferredComputed的activeEffect
  • 执行完this._get后,执行triggerRefValue(this),此dep中存放的是使用c1的activeEffect,---->执行trigger---->triggerEffects----->triggerEffect--->此activeEffect.scheduler存在,执行这个scheduler,执行triggerRefValue(this),此activeEffect.dep 存放使用c2的activeEffect,
  • activeEffect.run---->this.fn---->控制台打印effect,c2.value取值,执行get方法,
  • 使coumputed的dep收集activeEffect,再执行self._value = self.effect.run()!, 拿到c2.vlaue的值。
  private _get() {
    if (this._dirty) {
      this._dirty = false
      // 对firstName进行effect收集,获取新的this._value
      return (this._value = this.effect.run()!)
    }
    return this._value
  }
this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        // dep中时使用fullName的activeEffect,触发执行这些activeEffect.run,执行this.fn,重新获取fullNamde的值,
        // 触发get方法,执行trackRefValue,执行self.effect.run的执行,使firstName收集activeEffect.
        triggerRefValue(this)
      }
    })
  get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
    // 收集fullName 的effect。
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      // 就是去计算computed插入的回调的,收集firstName的effect
      self._value = self.effect.run()!
    }
    return self._value
  }