Vue3----ref原理解析

76 阅读2分钟

步骤一:createRef 方法

ref函数定义在packages/reactivity/src/ref.ts文件中:

export function ref(value?: unknown) {
  return createRef(value, false)
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

ref 函数实际执行的是 createRef 方法,而该方法实际是返回了一个 RefImpl 构造函数的实例对象:

步骤二:RefImpl 构造函数的实例对象:

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)
    // 操作值
    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)
    }
  }
}

步骤三:

RefImpl 构造函数会接收传入的值,可能是基本类型也可能是复杂类型,通过 _rawValue 记录原始值,用于之后依赖触发时新旧值的比较,我们需关注 this._value = __v_isShallow ? value : toReactive(value)toReactive 函数被定义在 packages/reactivity/src/reactive.ts 中:

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

步骤四:get方法核心trackRefvalue(this)触发,进行数据的依赖收集

步骤五:set方法中triggerRefValue(this, newVal)进行依赖触发:

总结:

  • ref 函数执行的是 createRef 方法,该方法实际是返回了一个 RefImpl 构造函数的实例对象
  • RefImpl构造函数接收传入的值,分为两种情况:提供get value和set value方法
    • 数据为基本类型,直接返回
    • 数据为复杂类型,调用reactive,返回reactive数据
  • RefImpl提供get valueset value方法
    • get方法核心trackRefvalue(this)触发,进行数据的依赖收集
    • set方法中triggerRefValue(this, newVal)进行依赖触发

为什么 ref 类型数据,必须要通过 .value 访问值呢?

a. 因为 ref 需要处理基本数据类型的响应性,但是对于基本类型数据而言,它无法通过 proxy 建立代理
b. 而 vue 通过 get value()set value() 定义了两个属性函数,通过主动触发这两个函数(属性调用)的形式来进行依赖收集和依赖触发
c. 所以我们必须通过 .value 来保证响应性。