vue3 中的响应式 reactivity(二)

149 阅读3分钟

vue3 中的响应式 reactivity(二)

effect.ts

class ReactiveEffect() --- effect.ts

ReactiveEffect 的实例就是依赖。deps 里存的 dep 就是 ReactiveEffect的实例, 创建一个包含响应式的副作用函数的对象。 因此ReactiveEffect类就是整个响应式变化执行的核心🌟

export class ReactiveEffect<T = any> {
  active = true // 当前是否再run()函数中, 即是是否为嵌套调用
  deps: Dep[] = []
  computed?: ComputedRefImpl<T> // 是否是computed 创建的
  /**
   * @internal
   */
  allowRecurse?: boolean // 是否允许递归
  onStop?: () => void 
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void
  /**
   * @internal
   */
  _dirtyLevel = DirtyLevels.Dirty
  /**
   * @internal
   */
  _trackId = 0
  /**
   * @internal
   */
  _runnings = 0
  /**
   * @internal
   */
  _shouldSchedule = false
  /**
   * @internal
   */
  _depsLength = 0

  constructor(
    public fn: () => T, // 副作用函数
    public trigger: () => void,
    public scheduler?: EffectScheduler,
    scope?: EffectScope,
  ) {
    recordEffectScope(this, scope)
  }

  // 读dirty值时调用
  public get dirty() {
    if (
      this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
      this._dirtyLevel === DirtyLevels.MaybeDirty
    ) {
      this._dirtyLevel = DirtyLevels.QueryingDirty
      pauseTracking()
      for (let i = 0; i < this._depsLength; i++) {
        const dep = this.deps[i]
        if (dep.computed) {
          triggerComputed(dep.computed)
          if (this._dirtyLevel >= DirtyLevels.Dirty) {
            break
          }
        }
      }
      if (this._dirtyLevel === DirtyLevels.QueryingDirty) {
        this._dirtyLevel = DirtyLevels.NotDirty
      }
      resetTracking()
    }
    return this._dirtyLevel >= DirtyLevels.Dirty
  }
  // 设置dirty时调用, 如 dirty = false时, _dirtyLevel = 0, 否则 = 4 
  public set dirty(v) {
    this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
  }

  /**
   * 
   *  1. 如果当前effect实例是不工作状态,就仅仅执行一下fn,不需要收集依赖。
      2. 由于在一个effect.run的过程中可能会触发另外的effect.run, 暂存上一次的activeEffect、shouldTrack,
         目的是为了本次执行完以后把activeEffect、shouldTrack恢复回去。
      3. 设置activeEffect、shouldTrack。
      4. runnings自增     
      6. 执行fn()。
      7. 在finally中主要是做一些善后的工作了:移除多余依赖、恢复activeEffect、shouldTrack、
      8. 如果deferStop 为 true,执行stop,可能在调用stop时,正在收集依赖,因此推迟到本次收集完成再stop。
   */
  run() {
    // 每次run函数执行后,_dirtyLevel = 0
    this._dirtyLevel = DirtyLevels.NotDirty
    if (!this.active) {
      // 不活跃状态,直接回调副作用函数
      return this.fn()
    }

    // 因为执行fn的过程中,可能会触发其他effect,形成嵌套调用
    // 当前上下文暂存 shouldTrack 和 activeEffect, 释放全局的activeEffect 和 shouldTrack
    let lastShouldTrack = shouldTrack 
    let lastEffect = activeEffect 
    try {
      shouldTrack = true
      activeEffect = this
      this._runnings++ // 记录正在运行的依赖数量
      preCleanupEffect(this) // 当前effect的深度记录为0
      return this.fn() // 执行当前副作用函数
    } finally {
      // 当前副作用函数执行结束,进行一些恢复操作
      postCleanupEffect(this)
      this._runnings--
      activeEffect = lastEffect
      shouldTrack = lastShouldTrack
    }
  }

  stop() {
    if (this.active) {
      preCleanupEffect(this)
      postCleanupEffect(this)
      this.onStop?.()
      this.active = false
    }
  }
}

trackEffect() --- effect.ts

回忆一下,refreactiveget方法最终实现依赖收集的方式都是执行trackEffect. trackEffect(activeEffect, ref.dep)

export function trackEffect(
  effect: ReactiveEffect,
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {

  // 依赖收集, 如果dep中不存在该effect, 执行dep.set()
  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId)
    // 检查收集的依赖是否需要更新
    const oldDep = effect.deps[effect._depsLength]
    if (oldDep !== dep) {
      if (oldDep) {
        cleanupDepEffect(oldDep, effect)
      }
      effect.deps[effect._depsLength++] = dep
    } else {
      effect._depsLength++
    }
    if (__DEV__) {
      effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
    }
  }
}

triggerEffects() --- triggerEffects.ts

export function triggerEffects(
  dep: Dep,
  dirtyLevel: DirtyLevels,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
  pauseScheduling()
  for (const effect of dep.keys()) {
    // 遍历dep, 依次执行effect.trigger()方法
    // dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
    let tracking: boolean | undefined
    if (
      effect._dirtyLevel < dirtyLevel &&
      (tracking ??= dep.get(effect) === effect._trackId)
    ) {
      effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
      effect._dirtyLevel = dirtyLevel
    }
    if (
      effect._shouldSchedule &&
      (tracking ??= dep.get(effect) === effect._trackId)
    ) {
      if (__DEV__) {
        effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
      }
      effect.trigger() // 确保 computed trigger 先执行
      if (
        (!effect._runnings || effect.allowRecurse) &&
        effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
      ) {
        effect._shouldSchedule = false
        if (effect.scheduler) {
          queueEffectSchedulers.push(effect.scheduler)
        }
      }
    }
  }
  resetScheduling()
}

一大堆的代码嗷,但核心操作就是

for (const effect of dep.keys()) {
    effect.trigger();
}

effect.trigger(); 则是在 ReactiveEffect 的构造函数里, 其实就是执行ReactiveEffect.run(),就是执行他的副作用函数.至此,整个响应式基本完成.

3.4 以前是直接调用effect.run()方法,而3.4以后是调用effect.trigger(),目的是重复调用的bug,确保computed先执行,参考

一些遗漏的点

mutableCollectionHandlers --- collectionHandlers.ts

针对es6 中的 collection 对象进行的封装, 如 Set Map WeakSet WeakMap 的方法进行封装 如对get,has,size,add,size,deleteEntry,clear方法进行track和trigger操作,这里只展示对get方法进行展示

function get(
  target: MapTypes,
  key: unknown,
  isReadonly = false,
  isShallow = false,
) {
  // #1772: readonly(reactive(Map)) should return readonly + reactive version
  // of the value
  target = (target as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    if (hasChanged(key, rawKey)) {
      track(rawTarget, TrackOpTypes.GET, key)
    }
    // 调用get方法时进行依赖收集
    track(rawTarget, TrackOpTypes.GET, rawKey)
  }
  const { has } = getProto(rawTarget)
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  if (has.call(rawTarget, key)) {
    return wrap(target.get(key))
  } else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  } else if (target !== rawTarget) {
    // #3602 readonly(reactive(Map))
    // ensure that the nested reactive `Map` can do tracking for itself
    target.get(key)
  }
}