Vue3 源码阅读,ReactiveEffect 以及相关函数

75 阅读3分钟

源码版本:3.2.37

普通的🌰

访问时添加依赖,更新时重新运行副作用函数

/* 响应式对象1 */
const r1 = ref(0)
/* 响应式对象2 */
const r2 = reactive({ p: 0 })

/* 创建 ReactiveEffect实例,并运行实例 run 方法 */
effect(() => {
  /* 访问对象,分别调用 trackRefValue 和 track */
  console.log(r1.value, r2.p) // 打印 0 0
  /* 调用 trigger,这里不会触发副作用函数运行 */
  r1.value++
})

/* 调用 triggerRefValue */
r1.value++ // 打印 2 0

/* 调用 trigger  */
r2.p++ // 打印 3 1

Dep:双方的桥梁

源码相对路径:packages/reactivity/src/dep.ts

dep对象,用于存储 ReactiveEffect实例,是响应式对象必备值,当响应式对象属性值发生变化时,就会遍历 dep对象 通知 ReactiveEffect实例 执行相关方法,实现“响应式更新”

创建对象

调用 createDep 生成 Set实例,并且在该实例设置 wn 两个数值类型属性:

  • w 表示 ReactiveEffect实例 已经追踪过该响应式对象的属性,ReactiveEffect实例 在执行副作用函数前对其赋值
  • n 表示 ReactiveEffect实例 在此次执行副作用函数时 首次访问该响应式对象的属性,在副作用函数访问响应式对象属性时,相关函数赋值
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}

辅助函数

辅助函数都绕不开 trackOpBit, 它是 effect.ts 代码模块里的变量,用于区分在 调用栈中 各个 ReactiveEffect实例 运行的副作用函数

wasTrackednewTracked 函数用来判断当前

export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0

export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0

initDepMarkers 在 ReactiveEffect实例 运行副作用函数 执行,用于对 ReactiveEffect实例 的所有依赖 dep对象 的 w 属性赋值,表示响应式对象的属性追踪过

export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      /* 标记 */
      deps[i].w |= trackOpBit
    }
  }
}

finalizeDepMarkers 在 ReactiveEffect实例 运行副作用函数 执行,清除 ReactiveEffect实例 所有 dep对象 关于此 trackOpBit

export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  const { deps } = effect
  if (deps.length) {
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
      const dep = deps[i]

      if (wasTracked(dep) && !newTracked(dep)) {
        /* 存在但未能访问到,删除多余数据 */
        dep.delete(effect)
      } else {
        deps[ptr++] = dep
      }

      /* 清除标记 */
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    deps.length = ptr
  }
}

ReactiveEffect

源码相对路径:packages/reactivity/src/effect.ts

effect函数

该函数接收副作用函数,并用其创建 ReactiveEffect实例,最后返回实例的 run 方法

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  /* 如果是 ReactiveEffect实例 的 run 方法时,获取真正的副作用函数 */
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  /* 创建实例 */
  const _effect = new ReactiveEffect(fn)

  /* 自定义设置 */
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }

  /* 可设置懒执行 */
  if (!options || !options.lazy) {
    _effect.run()
  }

  /* ReactiveEffect实例 的 run 方法 */
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

ReactiveEffect类

该类只需着重查看 run 方法即可

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

  computed?: ComputedRefImpl<T> /* computed 的 ReactiveEffect实例 */

  allowRecurse?: boolean
  private deferStop?: boolean

  onStop?: () => void

  // dev only

  constructor(
    public fn: () => T,/* 副作用函数 */
    public scheduler: EffectScheduler | null = null,/* 调度任务 */
    scope?: EffectScope
  ) {
    /* scope 用于统一关闭相关 ReactiveEffect实例 的响应式操作 */
    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

      /* 当前 ReactiveEffect实例 占用位 */
      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        /* 标记 dep对象的 w 属性 */
        initDepMarkers(this)
      } else {
        /* 旧模式,初始化 deps 属性,即清空双方关系 */
        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() {
    if (activeEffect === this) {
      /* 在副作用函数运行中调用 stop,推迟执行 */
      this.deferStop = true
    } else if (this.active) {
      /* 清空依赖 */
      cleanupEffect(this)
      /* 执行监听函数 */
      if (this.onStop) {
        this.onStop()
      }
      /* 标记其不参与响应式操作 */
      this.active = false
    }
  }
}

/* 解除双方关系 */
function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

track函数

该函数的目的是获取对象属性的 dep对象,最后调用 trackEffects。在访问响应式对象属性时,将调用 track;ref类型对象使用 trackRefValue

export function track(target: object, type: TrackOpTypes, key: unknown) {
  /* 检查当前 ReactiveEffect实例 能否追踪,是否存在实例对象 */
  if (shouldTrack && activeEffect) {
    /* 获取 target 保存的 map对象 */
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }

    /* 获取对应属性名的 dep对象 */
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }

    // 删除dev流程,eventInfo = undefined
    trackEffects(dep, eventInfo)
  }
}

// ref类型对象使用此函数
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    // 删除dev流程
    trackEffects(ref.dep || (ref.dep = createDep()))
  }
}

trackEffects函数

调用该函数添加 ReactiveEffect实例,ReactiveEffect实例 同样添加 dep对象

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  /* 是否添加当前运行的 ReactiveEffect实例 */
  let shouldTrack = false

  if (effectTrackDepth <= maxMarkerBits) {/* 位操作模式,优化性能 */
    /* 只在副作用函数第一次访问时操作 */
    if (!newTracked(dep)) {
      /* 标记 n 属性,声明访问过 */
      dep.n |= trackOpBit
      shouldTrack = !wasTracked(dep)
    }
  } else {
    /* 旧模式 */
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {/* 互相添加依赖 */
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    // 删除dev流程
  }
}

trigger函数

该函数的目的是获取对象属性的 dep对象,最后调用 triggerEffects。在设置响应式对象属性值时,将调用 trigger;ref类型对象使用 triggerRefValue

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) {/* 无相关依赖 */
    return
  }

  let deps: (Dep | undefined)[] = []

  // 根据数据类型和操作类型获取相关 dep对象,填充 deps。具体可查看源码

  // 删除dev流程
  if (deps.length === 1) {
    if (deps[0]) {
      // 删除dev流程
      triggerEffects(deps[0])
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    // 删除dev流程
    /* triggerEffects 接收的是 dep对象,但 deps 是数组,所以需要创建一个 */
    triggerEffects(createDep(effects))
  }
}

// ref类型对象使用此函数
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    // 删除dev流程
    triggerEffects(ref.dep)
  }
}

triggerEffects函数

该函数执行所有 dep对象 里的副作用函数,优先刷新 computed对象 的副作用函数(解决bug);优先执行 scheduler 任务

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  const effects = isArray(dep) ? dep : [...dep]
  /* 优先执行 computed对象 的 ReactiveEffect实例 任务 */
  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
) {
  /* 第一个条件:处理在副作用函数中设置响应式对象属性值的情况 */
  /* 第二个条件:
      参考 https://github.com/vuejs/core/issues/1801
           https://github.com/vuejs/core/issues/2043
  */
  if (effect !== activeEffect || effect.allowRecurse) {
    // 删除dev流程

    /* 优先执行 scheduler 任务 */
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}