Vue3-ref源码分析

359 阅读6分钟

ref

ref 如何使用

在 vue3 中使用ref的时候非常多,为何ref可以实现响应式,今天来通过源码阅读来一探究竟

先看一下ref是如何使用的:

import { ref } from 'vue'

// 基本数据类型
const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

// 复制数据类型
const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})
console.log(obj.value.nested.count) // 0
console.log(obj.value.arr) // ['foo', 'bar']

可以看到,ref不光可以定义简单数据类型,还可以定义复杂数据类型,访问的时候通过.value去获取

源码 位置: ref 。refshallowRef 实现一样,只是第二个参数不一样,一起看一下

本次查看的源码版本为 3.4.15

ref

// ref的导出函数,返回一个createRef
export function ref(value?: unknown) {
  //第一个为传入的值, 第二个参数表示是否shallow,浅比较
  return createRef(value, false)
}

shallowRef

export function shallowRef(value?: unknown) {
  // 传入true表示为shallow
  return createRef(value, true)
}

createRef

如果传入是一个ref值,则直接返回,否则创建一个RefImpl的实例

/**
 * 
 * @param rawValue  原始值
 * @param shallow 是否浅层
 * @returns 
 */
function createRef(rawValue: unknown, shallow: boolean) {
  // 本身果然就是ref值,则直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  // 返回一个RefImpl实例
  return new RefImpl(rawValue, shallow)
}

isRef

判断一个值是否是 ref 类型

export function isRef(r: any): r is Ref {
  // __v_isRef 为true就说明是一个ref值
  return !!(r && r.__v_isRef === true)
}

RefImpl

ref的核心实现,标记是一个ref类型, 值的获取&依赖收、 值的设置&依赖更新

class RefImpl<T> {
  private _value: T // 当前值
  private _rawValue: T // 原始值

  public dep?: Dep = undefined // dep 依赖收集
  public readonly __v_isRef = true // 标记为ref

  constructor(
    value: T,
    public readonly __v_isShallow: boolean, // 是否浅层
  ) {
    // 原始值。如果是浅层,则直接使用原始值,否则使用toRaw转换后的值
    this._rawValue = __v_isShallow ? value : toRaw(value) 
    // 如果是浅层,则直接使用原始值,否则使用toReactive转换后的值
    this._value = __v_isShallow ? value : toReactive(value)
  }
  // 获取值
  get value() {
    trackRefValue(this) // 收集依赖
    return this._value
  }
  // 设置值
  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    // 值有改变则进入
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal // 更新原始值
      this._value = useDirectValue ? newVal : toReactive(newVal) // 更新值
      triggerRefValue(this, DirtyLevels.Dirty, newVal) // 触发依赖
    }
  }
}

接下来看看依赖收集的逻辑

trackRefValue

依赖收集函数

shouldTrack activeEffect 存在时,将 ref.dep 放入 activeEffect.deps 中;调用 trackRefValue 时,ref.dep 不存在,所以会调用 createDep 创建一个 dep

/**
 * 
 * @param ref ref值
 */
export function trackRefValue(ref: RefBase<any>) {
  //  shouldTrack 能否收集依赖,activeEffect 当前正在运行的effect
  // shouldTrack 与 activeEffect 存在时,将 ref.dep 放入 activeEffect.deps 中
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    // 调用 trackRefValue 时,ref.dep 不存在,所以会调用 createDep 创建一个 dep
    trackEffect(
      activeEffect,
      ref.dep ||
        (ref.dep = createDep(
          () => (ref.dep = undefined), // 清除函数
          ref instanceof ComputedRefImpl ? ref : undefined, // 判断是否为computed
        )),
      __DEV__
        ? {
            target: ref,
            type: TrackOpTypes.GET,
            key: 'value',
          }
        : void 0,
    )
  }
}

triggerRefValue

依赖派发函数

/**
 * 
 * @param ref ref值
 * @param dirtyLevel dirty 层级
     3.4后 dep有改造,通过dirtyLevel 来做判断处理
     export enum DirtyLevels {
          NotDirty = 0, // 不是一个脏值
          MaybeDirty = 1, // 可能是一个脏值
          Dirty = 2, // 一般是computed的时候传入
        }

 * @param newVal 新值
 */
export function triggerRefValue(
  ref: RefBase<any>,
  dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
  newVal?: any,
) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    // 触发依赖
    triggerEffects(
      dep,
      dirtyLevel,
      __DEV__
        ? {
            target: ref,
            type: TriggerOpTypes.SET,
            key: 'value',
            newValue: newVal,
          }
        : void 0,
    )
  }
}

一些辅助的定义

  DirtyLevels 枚举定义
  export enum DirtyLevels {
      NotDirty = 0, // 不是一个脏值
      MaybeDirty = 1, // 可能是一个脏值
      Dirty = 2, // 一般是computed的时候传入
   }
   
// 转为原始值  
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

// Dep是一个Map类型,key是ReactiveEffect,value是number
export type Dep = Map<ReactiveEffect, number> & {
  cleanup: () => void
  computed?: ComputedRefImpl<any>
}
// 创建一个Dep
export const createDep = (
  cleanup: () => void,
  computed?: ComputedRefImpl<any>,
): Dep => {
  const dep = new Map() as Dep // 创建一个Map
  dep.cleanup = cleanup // cleanup是一个函数,用于清除依赖
  dep.computed = computed // computed是一个计算属性
  return dep
}

接下来看看依赖收集与依赖更新的代码

effect

代码位于 effect

trackEffect

依赖收集,把activeEffect添加到dep

/**
 * 
 * @param effect 当前激活的副作用函数
 * @param dep 依赖列表
 * @param debuggerEventExtraInfo 
 */
export function trackEffect(
  effect: ReactiveEffect,
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
  // 判断dep中的efft是否已经被追踪过了, 未追踪过的话, 就将effect添加到dep中
  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId) // 添加effect到dep中
    const oldDep = effect.deps[effect._depsLength] // 获取effect上一次追踪的dep
    // 如果当前的dep和上一次的dep不一样, 就将effect从上一次的dep中移除, 并将effect添加到当前的dep中
    if (oldDep !== dep) {
      if (oldDep) {
        // 清除掉老的dep中的的effect
        cleanupDepEffect(oldDep, effect)
      }
      effect.deps[effect._depsLength++] = dep
    } else {
      //  更新_depsLength的长度
      effect._depsLength++
    }
    if (__DEV__) {
      effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
    }
  }
}

triggerEffects

遍历 dep 中收集到的 effect,

/**
 * 
 * @param dep dep依赖收集器
 * @param dirtyLevel  脏等级
 * @param debuggerEventExtraInfo 
 */
export function triggerEffects(
  dep: Dep,
  dirtyLevel: DirtyLevels,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
  
  pauseScheduling() // pauseScheduleStack 自增1
  // 遍历收集到的effect
  for (const effect of dep.keys()) {
    // 当effect的脏等级小于当前的脏等级, 并且effect的trackId等于dep的trackId
    if (
      effect._dirtyLevel < dirtyLevel &&
      dep.get(effect) === effect._trackId
    ) {
      // 获取effect的_dirtyLevel
      const lastDirtyLevel = effect._dirtyLevel
      // 设置effect的_dirtyLevel为传入的dirtyLevel
      effect._dirtyLevel = dirtyLevel
      // 如果effect的_dirtyLevel等于DirtyLevels.NotDirty(0)
      if (lastDirtyLevel === DirtyLevels.NotDirty) {
        // 设置_shouldSchedule为true
        effect._shouldSchedule = true
        if (__DEV__) {
          effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
        }
        // 触发effect 上的 trigger方法
        effect.trigger()
      }
    }
  }
  // 遍历dep收集的effect,把effect的scheduler方法添加到queueEffectSchedulers中
  scheduleEffects(dep)
  resetScheduling() // pauseScheduleStack 自减1,queueEffectSchedulers 清空
}

scheduleEffects

queueEffectSchedulers添加effect中的scheduler

export function scheduleEffects(dep: Dep) {
  for (const effect of dep.keys()) {
    if (
      effect.scheduler &&
      effect._shouldSchedule &&
      (!effect._runnings || effect.allowRecurse) &&
      dep.get(effect) === effect._trackId
    ) {
      // 设置effect的_shouldSchedule为false
      effect._shouldSchedule = false
      // 把effect的scheduler方法添加到queueEffectSchedulers中
      queueEffectSchedulers.push(effect.scheduler)
    }
  }
}

ref收集依赖的时候判断 activeEffectshouldTrack是否存在,那这个activeEffectshouldTrack是什么时候才有的呢?

在界面初始化渲染的时候 调用了ReactiveEffect,执行effect.run 赋值的、

代码位置 setupRenderEffect

  const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    namespace: ElementNamespace,
    optimized,
  ) => {
    // 调度函数
     const componentUpdateFn = () => {
         // 省略部分代码
     }
        // 创建一个副作用收集函数
    // create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      NOOP,
      () => queueJob(update),
      instance.scope, // track it in component's effect scope
    ))
    // 更新方法
    const update: SchedulerJob = (instance.update = () => {
      if (effect.dirty) {
        effect.run()
      }
    })
    update.id = instance.uid
    // allowRecurse
    // #1801, #2043 component render effects should allow recursive updates
    toggleRecurse(instance, true)

    // 调用
    update()
  }

ReactiveEffect

设置执行函数,调度器等,运行run方法时候把shouldTrackactiveEffect设置为当前的 this,执行完后回退到上一个执行依赖的地方,保证每一次都是最新的收集

export class ReactiveEffect<T = any> {
  active = true // 是否激活
  deps: Dep[] = [] // 依赖项

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T> // 计算属性
  /**
   * @internal
   */
  allowRecurse?: boolean  

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

  /**
   * @internal
   */
  _dirtyLevel = DirtyLevels.Dirty // 脏等级
  /**
   * @internal
   */
  _trackId = 0 // 跟踪id
  /**
   * @internal
   */
  _runnings = 0 // 运行次数
  /**
   * @internal
   */
  _shouldSchedule = false // 是否应该调度
  /**
   * @internal
   */
  _depsLength = 0 // 依赖项长度

  constructor(
    public fn: () => T, // 函数
    public trigger: () => void, // 触发器
    public scheduler?: EffectScheduler, // 调度器,如传入则执行该调度器
    scope?: EffectScope,
  ) {
    recordEffectScope(this, scope)
  }
  // 获取effect的dirty值
  public get dirty() {
    if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
      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.Dirty) {
        this._dirtyLevel = DirtyLevels.NotDirty
      }
      resetTracking()
    }
    return this._dirtyLevel >= DirtyLevels.Dirty
  }
  // 设置effect的dirty值
  public set dirty(v) {
    this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
  }

  run() {
    this._dirtyLevel = DirtyLevels.NotDirty
    // 不是激活状态直接调用传入的方法
    if (!this.active) {
      return this.fn()
    }
    let lastShouldTrack = shouldTrack
    let lastEffect = activeEffect
    try {
      shouldTrack = true // 设置为true
      activeEffect = this // 设置当前激活的effect
      this._runnings++
      preCleanupEffect(this)
      return this.fn()  // 执行传入的方法
    } finally {
      postCleanupEffect(this)
      this._runnings--
      activeEffect = lastEffect // 恢复上一个激活的effect
      shouldTrack = lastShouldTrack // 恢复上一个shouldTrack
    }
  }
  // 停止收集
  stop() {
    if (this.active) {
      preCleanupEffect(this)
      postCleanupEffect(this)
      this.onStop?.()
      this.active = false
    }
  }
}