vue3源码分析之computed

computed:

一、入口函数:

代码的核心是实例化对象 ComputedRefImpl ;有一个需要注意的点是会判断 getterOrOptions 是否是function,不是的话会直接调用并采用对象里的方法(就是计算属性的可写方式传入的代码),大部分我们都是采用可读的方式调用计算属性就是传入一个函数方法。

export function computed<T>(
  // getterOrOptions 是计算属性接收函数方法
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  
  // getterOrOptions 在这里进行判断的 typeof 是否是 function
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    getter = getterOrOptions
    // export const NOOP = () => {}
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  
  // 核心代码,实例化 ComputedRefImpl 对象
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)

  if (__DEV__ && debugOptions && !isSSR) {
    cRef.effect.onTrack = debugOptions.onTrack
    cRef.effect.onTrigger = debugOptions.onTrigger
  }

  return cRef as any
}
二、实际调用方法(被实例化的类):

*****首先需要明确一点,我们在创建一个 class 类的时候,constructor(构造函数)方法会创建和初始化class创建的对象

初始化

constructor 的初始化过程:

第一步:调用了 ReactiveEffect 这个类,在第一次调用这个类的时候只会传入getter,之后会调用传入的第二个参数,第二个参数是计算属性的核心之处,在这里会判断是否需要重新计算。(在会面会分析ReactiveEffect

第二步:各种赋值操作(可见代码)

export class ComputedRefImpl<T> {
  public dep?: Dep = undefined

  private _value!: T
  // readonly 是一个只读属性,这里也是个关键
  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() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
    // 这一步是为dep添加了 activeEffect
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      // 后面的感叹号是 非空断言操作符 
      self._value = self.effect.run()!
    }
    return self._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }
}
ReactiveEffect 类的调用:

初始化的时候调用方法 recordEffectScope(this, scope)

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean
  /**
   * @internal
   */
  private deferStop?: boolean

  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    // 主要执行这么一段代码 scope.effects.push(effect)
    recordEffectScope(this, scope)
  }

  run() {
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      // 在这里进行一个比较,如果上一次的缓存数据和当前一样不会再执行,直接返回
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        cleanupEffect(this)
      }
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }

  stop() {
    // stopped while running itself - defer the cleanup
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}
调用 recordEffectScope 方法:

在这一步会向 effects 中添加 effect

export function recordEffectScope(
  effect: ReactiveEffect,
  scope: EffectScope | undefined = activeEffectScope
) {
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}
第一次调用 computed 方法执行过程:

在第一次调用的时候,会执行ComputedRefImpll类中的value()方法,但是首先会执行 constructor 的初始化,由于是第一次执行所以this._dirty是true,所以传入 ReactiveEffect 中的参数只有一个 ReactiveEffect(getter)。

详细分析这一步:

第一步:construct 初始化,this._dirty 为 true 所以传入的参数只有一个 ReactiveEffect(getter)。一定要记住这一步的 this._dirty ,在后需要他会起到是否执行计算计算方法的关键作用。

第二步:执行 value 方法

a:执行 trackRefValue() 方法,向 dep 中添加 activeEffect, 并向deps数组中添加 dep 代码:activeEffect!.deps.push(dep)

b将 self._dirty 设置为 false,并调用实例化后的类this.effect执行它里面的run 方法最后返回 _value 值(下一步分析 run 方法)

export class ComputedRefImpl<T> {
  public dep?: Dep = undefined

  private _value!: T
  // readonly 是一个只读属性
  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() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
    // 这一步是为dep添加了 activeEffect
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      // 后面的感叹号是 非空断言操作符 
      self._value = self.effect.run()!
    }
    return self._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }
}
执行run方法:

run() 方法是在类 ReactiveEffect 的方法(详细代码见上面的调用 recordEffectScope 方法),在这一步主要做的操作是将当前this赋值给 activeEffect ,在下一次执行的时候会判断上一次保存的 activeEffect 和当前的 this 是否相同,如果相同的话直接返回不再进行计算属性的执行。

 run() {
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      // 在这里进行一个比较,如果上一次的缓存数据和当前一样不会再执行,直接返回
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        cleanupEffect(this)
      }
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }
第二次以及之后调用 computed 方法执行过程:

继续探讨 ComputedRefImpl 类,由于是第二次调用,所以 ReactiveEffect 传入的是两个参数

  this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        // 在这里又将 this._dirty 设置为了 true
        this._dirty = true
        triggerRefValue(this)
      }
    })

分析一下,triggerRefValue() 方法做了什么

调用 triggerEffects(ref.dep) 方法

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      //  这一步将 dep 解构且生成 effects 数组,首先判断是否具有这两个方法之后再调用 scheduler() 和 run() 方法
      triggerEffects(ref.dep)   
    }
  }
}

分析 triggerEffects(ref.dep) 方法:

这一步将 dep 解构且生成 effects 数组,首先判断是否具有这两个方法之后再调用 scheduler() 和 run() 方法,在这里是执行了 run 方法

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

执行的run()方法和之前一样,就不在进行讨论了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值