Vue3.0源码学习——响应式原理(三)

574 阅读3分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。

前言

前篇 Vue3.0源码学习——响应式原理(一)Vue3.0源码学习——响应式原理(二) 通过reactive 源码的学习和单步调试了解了Vue3.0 响应式的原理,内部是使用了 Proxygetter 去收集数据的依赖 dep,这个依赖其实就是组件的更新函数 activeEffect,然后在 setter 中去触发依赖中的组件更新函数,从而达到了响应式的效果,这一篇将学习另外一个响应式API ref 的源码

ref

ref文档 refreactive 都可以将数据转为响应式,区别在于 ref 接受一个内部值并返回一个响对象,reactive 返回对象的响应式副本,因此一般在 ref 中使用单值,reactive 中使用对象,但是 ref 中也可以使用对象

源码分析

这里使用 Vscode 查看源码,首先定位 ref 函数的位置,ctrl + t 查找 ref()

  • ref 函数位置 packages\reactivity\src\ref.ts 可以看到有多个 ref 函数的类型定义
export function ref<T extends object>(
  value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
  • 因此在平时开发中如果使用 typeScript 以下两种方式都可以约束类型
const numberData = ref<number>(1)

const stringData: Ref<string> = ref('')
  • ref 函数中返回了一个 createRef 函数把我们定义的数据传入,并加上了第二个参数 false
export function ref(value?: unknown) {
  return createRef(value, false)
}
  • createRef 函数中返回了一个 RefImpl 类的实例,并将自身的参数原封不动传入
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
  • RefImpl 如果 constructor 的第二个参数 shallowtrue 就不转为响应式数据,之前 ref 传入的第二个参数显然是 false 因此会转为响应式数据 toReactive,然后对传入的 value 进行 getset 的操作,因此在使用 ref 时需要使用 .vaule
class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // 深层次用toReactive递归转为reactive
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      // 触发副作用
      triggerRefValue(this, newVal)
    }
  }
}
  • 这里可以看一下 toReactive 函数,会判断 ref 中传入的值的类型,如果是对象类型就会调用 reactive 函数,因此 ref 中也可以使用对象类型的数据
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value
  • get 会在返回数据前,包一层 trackRefValue,其中关键就是对数据依赖 dep 的收集,然后触发 trackEffects 跟踪依赖
export function trackRefValue(ref: RefBase<any>) {
  if (isTracking()) {
    ref = toRaw(ref)
    // 为Ref实例创建一个依赖
    if (!ref.dep) {
      // 一个由RectiveEffect构成的Set
      ref.dep = createDep()
    }
    if (__DEV__) {
      trackEffects(ref.dep, {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      // 依赖收集
      trackEffects(ref.dep)
    }
  }
}
  • set 会在设置新值的时候执行 triggerRefValue 函数,并且触发副作用函数 triggerEffects
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(ref.dep)
    }
  }
}
  • 到这里就知道了 getset 做的事情和 reactive 是一样的了

ref函数执行流程图

图片.png