Vue3 源码解析系列 - 依赖收集和派发更新

329

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

前言

上篇我们了解了 effect 的用法和作用,并了解到了依赖收集到派发更新的具体流程,这节我们就详细看看如何进行依赖收集和派发更新。

依赖收集 track

我在前面的 reactive 方法中讲到了,我们会将对象通过 Proxy 转换成代理对象,并劫持 get 方法、set方法。而在 get 方法中就行进行依赖收集。

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    /** 省略 */
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
     /** 省略 */
  }
}

track 就是依赖收集的方法,传入三个参数,分别是目标对象target、get、具体访问的那个key。

track 方法的源码位于 packages/reactivity/src/effect.ts

// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}
  1. 首先判断 activeEffect 是否存在,也就是当前是否有 effect 被执行,没有的话就不进行收集
  2. 然后再判断 targetMap 中是否已经存在了 target 的depsMap,不存在就初始化一个depsMap
  3. 判断 depsMap 是否已经有这个key的依赖对象,没有就创建一个 new Set() 对象
  4. 最后调用 trackEffects 传入 dep
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
  }
}

在 trackEffects 中将当前执行的 activeEffect 保存到 dep依赖中,所以每个依赖也是一个 effect 函数,到时候派发更新时调用 effect.run 就能更新了。

派发更新 trigger

派发更新是在 set 陷阱中,当设置目标值是会触发。

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

这里会调用 trigger 方法,如果是新增,就传入目标对象、add、key和value。
如果有旧值的存在则,传入目标对象、set、key、新的value 和 旧的value

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
   triggerEffects(createDep(effects))
  }
}

它根据 target 从 targetMap 中获取到了 depsMap,然后再把 depsMap 中的所有依赖 effect 函数,放入数组 deps 中。 然后triggerEffects 进行遍历,最终分别调用 triggerEffect 方法

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()
    }
  }
}

在 triggerEffect 中,调用了每个依赖的 run 方法,我们从前面的例子中得知,在run中会执行我们传进行的更新方法,最后成功更新所有依赖。

小结

这篇更详细的了解了如何进行依赖收集和更新,并知道了,其实每个依赖都是一个effect函数。