Vue源码学习=>reactivity=>ref

ref的定义

js 复制代码
/**
 * 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 函数接受两个参数:rawValueshallowrawValue 是传入的值,shallow 是一个布尔值,表示是否进行浅响应。

创建引用对象的方法:createRef

js 复制代码
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 类的实例。

js 复制代码
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 属性就会被跟踪。

js 复制代码
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 属性的副作用。

js 复制代码
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可以将传入的值转换为响应式引用对象

js 复制代码
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函数的实现

js 复制代码
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 函数接收三个参数:sourcekeydefaultValue。根据 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方法

js 复制代码
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) // 如果源对象的属性值已经是一个引用对象,则直接返回该引用对象。否则,
                                                           //创建一个新的引用对象,并将源对象、属性键名和默认值作为参数传入构造函数。
}
相关推荐
m0_740043732 小时前
3、Vuex-Axios-Element UI
前端·javascript·vue.js
鹏北海2 小时前
微信扫码登录 iframe 方案中的状态拦截陷阱
前端·javascript·vue.js
狗哥哥2 小时前
Vite 插件实战 v2:让 keep-alive 的“组件名”自动长出来
前端·vue.js·架构
小黑的铁粉2 小时前
Vue2 vs Vue3
vue.js
AAA阿giao2 小时前
代码宇宙的精密蓝图:深入探索 Vue 3 + Vite 项目的灵魂结构
前端·javascript·vue.js
半桶水专家2 小时前
vue中的props详解
前端·javascript·vue.js
前端不太难2 小时前
RN 遇到复杂手势(缩放、拖拽、旋转)时怎么设计架构
javascript·vue.js·架构
白兰地空瓶2 小时前
一行 npm init vite,前端工程化的世界就此展开
前端·vue.js·vite
码力巨能编3 小时前
Markdown 作为 Vue 组件导入
前端·javascript·vue.js
仰望.3 小时前
vue 甘特图 vxe-gantt table 拖拽任务调整开始日期和结束日期的使用,拖拽任务调整日期
vue.js·甘特图·vxe-ui