effect

effect 作为 reactive 的核心,主要负责收集依赖,更新依赖

如果你还没看过 reactive , 建议点这里

文档直通车: https://vue3js.cn/vue-composition-api/#reactive

正文

我们还是选择先从定义看起

effect 接收两个参数

  • fn 回调函数
  • options 参数
  1. export interface ReactiveEffectOptions {
  2. lazy?: boolean // 是否延迟触发 effect
  3. computed?: boolean // 是否为计算属性
  4. scheduler?: (job: ReactiveEffect) => void // 调度函数
  5. onTrack?: (event: DebuggerEvent) => void // 追踪时触发
  6. onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
  7. onStop?: () => void // 停止监听时触发
  8. }
  9. export function effect<T = any>(
  10. fn: () => T,
  11. options: ReactiveEffectOptions = EMPTY_OBJ
  12. ): ReactiveEffect<T> {
  13. // 如果已经是 `effect` 先重置为原始对象
  14. if (isEffect(fn)) {
  15. fn = fn.raw
  16. }
  17. // 创建`effect`
  18. const effect = createReactiveEffect(fn, options)
  19. // 如果没有传入 lazy 则直接执行一次 `effect`
  20. if (!options.lazy) {
  21. effect()
  22. }
  23. return effect
  24. }

入口很简单,我们继续看一下 effect 是怎么创建的,如果下面这些你觉得相对难理解,建议先去看一遍单元测试,单元测试会助你快速了解整个单元的目的

  1. function createReactiveEffect<T = any>(
  2. fn: (...args: any[]) => T,
  3. options: ReactiveEffectOptions
  4. ): ReactiveEffect<T> {
  5. const effect = function reactiveEffect(...args: unknown[]): unknown {
  6. // 没有激活,说明我们调用了effect stop 函数,
  7. if (!effect.active) {
  8. // 如果没有调度者,直接返回,否则直接执行fn
  9. return options.scheduler ? undefined : fn(...args)
  10. }
  11. // 判断effectStack中有没有effect, 如果在则不处理
  12. if (!effectStack.includes(effect)) {
  13. // 清除effect依赖,定义在下方
  14. cleanup(effect)
  15. try {
  16. // 开始重新收集依赖
  17. enableTracking()
  18. // 压入Stack
  19. effectStack.push(effect)
  20. // 将activeEffect当前effect
  21. activeEffect = effect
  22. return fn(...args)
  23. } finally {
  24. // 完成后将effect弹出
  25. effectStack.pop()
  26. // 重置依赖
  27. resetTracking()
  28. // 重置activeEffect
  29. activeEffect = effectStack[effectStack.length - 1]
  30. }
  31. }
  32. } as ReactiveEffect
  33. effect.id = uid++ // 自增id, effect唯一标识
  34. effect._isEffect = true // 是否是effect
  35. effect.active = true // 是否激活
  36. effect.raw = fn // 挂载原始对象
  37. effect.deps = [] // 当前 effect 的dep 数组
  38. effect.options = options // 传入的options,在effect有解释的那个字段
  39. return effect
  40. }
  41. const effectStack: ReactiveEffect[] = []
  42. // 每次 effect 运行都会重新收集依赖, deps 是 effect 的依赖数组, 需要全部清空
  43. function cleanup(effect: ReactiveEffect) {
  44. const { deps } = effect
  45. if (deps.length) {
  46. for (let i = 0; i < deps.length; i++) {
  47. deps[i].delete(effect)
  48. }
  49. deps.length = 0
  50. }
  51. }

看到这里应该会有一个问题了?effect 是如何收集及触发依赖的呢?现在我们回想一下在reactive高频出现的两个函数

  • track 收集依赖(get操作)
  • trigger 触发依赖(触发更新后执行监听函数之前触发)

track

  1. /**
  2. * @description:
  3. * @param {target} 目标对象
  4. * @param {type} 收集的类型, 函数的定义在下方
  5. * @param {key} 触发 track 的 object 的 key
  6. */
  7. export function track(target: object, type: TrackOpTypes, key: unknown) {
  8. // activeEffect为空代表没有依赖,直接return
  9. if (!shouldTrack || activeEffect === undefined) {
  10. return
  11. }
  12. // targetMap 依赖管理中心,用于收集依赖和触发依赖
  13. // 检查targetMap中有没有当前target
  14. let depsMap = targetMap.get(target)
  15. if (!depsMap) {
  16. // 没有则新建一个
  17. targetMap.set(target, (depsMap = new Map()))
  18. }
  19. // deps 来收集依赖函数,当监听的 key 值发生变化时,触发 dep 中的依赖函数
  20. let dep = depsMap.get(key)
  21. if (!dep) {
  22. depsMap.set(key, (dep = new Set()))
  23. }
  24. if (!dep.has(activeEffect)) {
  25. dep.add(activeEffect)
  26. activeEffect.deps.push(dep)
  27. // 开发环境会触发onTrack, 仅用于调试
  28. if (__DEV__ && activeEffect.options.onTrack) {
  29. activeEffect.options.onTrack({
  30. effect: activeEffect,
  31. target,
  32. type,
  33. key
  34. })
  35. }
  36. }
  37. }
  38. // get、 has、 iterate 三种类型的读取对象会触发 track
  39. export const enum TrackOpTypes {
  40. GET = 'get',
  41. HAS = 'has',
  42. ITERATE = 'iterate'
  43. }

trigger

  1. export function trigger(
  2. target: object,
  3. type: TriggerOpTypes,
  4. key?: unknown,
  5. newValue?: unknown,
  6. oldValue?: unknown,
  7. oldTarget?: Map<unknown, unknown> | Set<unknown>
  8. ) {
  9. const depsMap = targetMap.get(target)
  10. // 依赖管理中没有, 代表没有收集过依赖,直接返回
  11. if (!depsMap) {
  12. // never been tracked
  13. return
  14. }
  15. // 对依赖进行分类
  16. // effects 代表普通依赖,
  17. // computedRunners 为计算属性依赖
  18. // 都是 Set 结构,避免重复收集
  19. const effects = new Set<ReactiveEffect>()
  20. const computedRunners = new Set<ReactiveEffect>()
  21. const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
  22. if (effectsToAdd) {
  23. effectsToAdd.forEach(effect => {
  24. // 避免重复收集
  25. if (effect !== activeEffect || !shouldTrack) {
  26. // 计算属性依赖
  27. if (effect.options.computed) {
  28. computedRunners.add(effect)
  29. } else {
  30. // 普通属性依赖
  31. effects.add(effect)
  32. }
  33. } else {
  34. // the effect mutated its own dependency during its execution.
  35. // this can be caused by operations like foo.value++
  36. // do not trigger or we end in an infinite loop
  37. }
  38. })
  39. }
  40. }
  41. if (type === TriggerOpTypes.CLEAR) {
  42. // collection being cleared
  43. // trigger all effects for target
  44. depsMap.forEach(add)
  45. } else if (key === 'length' && isArray(target)) {
  46. depsMap.forEach((dep, key) => {
  47. if (key === 'length' || key >= (newValue as number)) {
  48. add(dep)
  49. }
  50. })
  51. } else {
  52. // schedule runs for SET | ADD | DELETE
  53. if (key !== void 0) {
  54. add(depsMap.get(key))
  55. }
  56. // also run for iteration key on ADD | DELETE | Map.SET
  57. const isAddOrDelete =
  58. type === TriggerOpTypes.ADD ||
  59. (type === TriggerOpTypes.DELETE && !isArray(target))
  60. if (
  61. isAddOrDelete ||
  62. (type === TriggerOpTypes.SET && target instanceof Map)
  63. ) {
  64. add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
  65. }
  66. if (isAddOrDelete && target instanceof Map) {
  67. add(depsMap.get(MAP_KEY_ITERATE_KEY))
  68. }
  69. }
  70. const run = (effect: ReactiveEffect) => {
  71. if (__DEV__ && effect.options.onTrigger) {
  72. effect.options.onTrigger({
  73. effect,
  74. target,
  75. key,
  76. type,
  77. newValue,
  78. oldValue,
  79. oldTarget
  80. })
  81. }
  82. // 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
  83. if (effect.options.scheduler) {
  84. effect.options.scheduler(effect)
  85. } else {
  86. effect()
  87. }
  88. }
  89. // Important: computed effects must be run first so that computed getters
  90. // can be invalidated before any normal effects that depend on them are run.
  91. // 触发依赖函数
  92. computedRunners.forEach(run)
  93. effects.forEach(run)
  94. }