Vue源码学习=>reactivity=>ref

ref的定义

/**
 * Takes an inner value and returns a reactive and mutable ref object, which
 * has a single property `.value` that points to the inner value.
 * @param value - The object to wrap in the ref.
 * @see {@link https://vuejs.org/api/reactivity-core.html#ref}
 */
export function ref<T>(value: T): Ref<UnwrapRef<T>> // 重载1:接受一个内部值并返回一个响应式且可变的 ref 对象
export function ref<T = any>(): Ref<T | undefined> // 重载2:不接受参数并返回一个响应式且可变的 ref 对象
export function ref(value?: unknown) {  
  return createRef(value, false) 
}

以上即Vue3中ref函数的定义,ref函数有两个重载版本。

第一个版本 ref<T>(value: T) 接受一个参数 value,并返回一个类型为 Ref<UnwrapRef<T>> 的引用对象。这个引用对象的 .value 属性指向传入的值。

第二个版本 ref<T = any>() 不接受参数,返回一个类型为 Ref<T | undefined> 的引用对象。这个引用对象的 .value 属性的初始值为 undefined

在 ref 函数的实现中,首先调用 createRef 函数来创建一个新的引用对象。createRef 函数接受两个参数:rawValue 和 shallowrawValue 是传入的值,shallow 是一个布尔值,表示是否进行浅响应。

创建引用对象的方法:createRef

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) { 
    return rawValue // 如果传入的值已经是引用对象,则直接返回该值。
  }
  return new RefImpl(rawValue, shallow) // 否则,创建一个新的引用对象并返回。
}

isRef 函数是用来检查一个值是否是引用对象(Ref object)。它会检查这个值是否有一个名为__v_isRef 的属性,且该属性的值为 true。如果有,那么这个值就是一个引用对象。

RefImpl

RefImpl 是 Vue中的一个类,用于实现响应式引用对象。当你在 Vue.js 中使用 ref 函数创建一个响应式引用对象时,实际上就是创建了一个 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) // 如果是浅层引用,则直接使用原始值,否则使用 toRaw 函数将值转换为响应式对象
    this._value = __v_isShallow ? value : toReactive(value) // 如果是浅层引用,则直接使用原始值,否则使用 toReactive 函数将值转换为响应式对象
  }

   // 当试图获取引用对象的.value 属性时,会调用这个方法。
  get value() { // 获取引用对象的值
    trackRefValue(this) // 跟踪引用对象的值
    return this._value // 返回引用对象的值
  }


   // 引用对象的实现类的 set 方法,用于设置引用对象的值。
   // 当修改引用对象的.value 属性时,会调用这个方法。
 
  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)  // 如果是浅层引用,则直接使用原始值,否则使用 toRaw 函数将值转换为响应式对象
    if (hasChanged(newVal, this._rawValue)) { // 判断新值和原始值是否相等
      this._rawValue = newVal // 更新原始值
      this._value = useDirectValue ? newVal : toReactive(newVal) // 更新引用对象的值
      triggerRefValue(this, DirtyLevels.Dirty, newVal) // 触发引用对象的依赖项
    }
  }
}

跟踪引用值的方法:trackRefValue

当一个响应式引用对象的 .value 属性被访问时,trackRefValue 函数就会被调用,然后这个 .value 属性就会被跟踪。

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    trackEffect(
      activeEffect,
      ref.dep ||
        (ref.dep = createDep(
          () => (ref.dep = undefined),
          ref instanceof ComputedRefImpl ? ref : undefined,
        )),
      __DEV__
        ? {
            target: ref,
            type: TrackOpTypes.GET,
            key: 'value',
          }
        : void 0,
    )
  }
}

触发引用值的方法:triggerRefValue

当响应式引用对象的 .value 属性的值发生变化时,触发所有依赖于这个 .value 属性的副作用。

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,
    )
  }
}

toRef

toRef可以将传入的值转换为响应式引用对象

export function toRef<T>(
  value: T,
): T extends () => infer R //  T extends () => infer R 用于判断 T 是否是一个函数
  ? Readonly<Ref<R>> // T extends () => infer R 的结果为 true, R 这个函数的返回类型。toRef 函数就会返回一个只读的响应式引用,这个响应式引用的 .value 属性的类型是 R。
  : T extends Ref // T extends Ref 用于判断 T 是否是一个引用对象
    ? T  // T extends Ref 的结果为 true,toRef 函数就会直接返回这个引用对象。
    : Ref<UnwrapRef<T>> // T extends Ref 的结果为 false,toRef 函数就会将 T 包装为一个引用对象。

  
  
  export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
): ToRef<T[K]> //ToRef<T[K]> 是一个条件类型, 如果 T[K] 是一个引用对象,则返回 T[K],否则返回 Ref<T[K]>,而且这个响应式引用的 .value 属性的类型是 T[K] 。


export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue: T[K],
): ToRef<Exclude<T[K], undefined>> // 如果 T[K] 是 undefined,则返回 never,否则返回 T[K]。

toRef有三个重载:

  • 重载1:如果值是一个函数,则返回一个只读的引用对象,该引用对象在访问 .value 时调用该函数获取值。

  • 重载2:如果值是一个对象且提供了键名,则返回该对象的属性引用。

  • 重载3:如果值是一个对象且提供了键名和默认值,则返回该对象的属性引用,并将默认值作为初始值。

    最后是toRef函数的实现

export function toRef(
  source: Record<string, any> | MaybeRef,
  key?: string,
  defaultValue?: unknown,
): Ref {
  if (isRef(source)) {
    return source // 如果参数是一个引用对象,则直接返回该引用对象。
  } else if (isFunction(source)) {
    return new GetterRefImpl(source) as any // 如果参数是一个函数,则返回一个只读的引用对象,该引用对象在访问 `.value` 时调用该函数获取值。
  } else if (isObject(source) && arguments.length > 1) {
    return propertyToRef(source, key!, defaultValue) // 如果参数是一个对象且提供了键名,则返回该对象的属性引用。
  } else {
    return ref(source) // 如果参数不满足以上条件,则将其包装为一个引用对象。
  }
}

toRef 函数接收三个参数:sourcekey 和 defaultValue。根据 source 的类型和参数的数量:

  • 如果 source 是一个引用对象(通过 isRef 函数检查),那么 toRef 函数会直接返回这个引用对象。
  • 如果 source 是一个函数,那么 toRef 函数会返回一个只读的引用对象,这个引用对象在访问 .value 时会调用 source 函数获取值。这是通过创建 GetterRefImpl 类的实例来实现的,GetterRefImpl 类在访问 .value 属性时会调用传入的 getter 函数。
  • 如果 source 是一个对象且提供了 key 参数,那么 toRef 函数会返回 source 对象的 key 属性的引用。这是通过调用 propertyToRef 函数来实现的,propertyToRef 函数会检查 source[key] 是否已经是一个引用对象,如果是则直接返回,否则会创建一个新的 ObjectRefImpl 类的实例。
  • 如果 source 不满足以上任何条件,那么 toRef 函数会调用 ref 函数将 source 包装为一个引用对象。

propertyToRef方法

function propertyToRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown,
) {
  const val = source[key] // 获取源对象的属性值
  return isRef(val)
    ? val
    : (new ObjectRefImpl(source, key, defaultValue) as any) // 如果源对象的属性值已经是一个引用对象,则直接返回该引用对象。否则,
                                                           //创建一个新的引用对象,并将源对象、属性键名和默认值作为参数传入构造函数。
}

原文链接:https://juejin.cn/post/7324585777635754024 作者:Star768

(0)
上一篇 2024年1月17日 下午4:55
下一篇 2024年1月17日 下午5:05

相关推荐

发表回复

登录后才能评论