【Vue3 源码解析】to 系列全家桶

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
  } else if (isObject(source) && arguments.length > 1) {
    return propertyToRef(source, key!, defaultValue)
  } else {
    return ref(source)
  }
}

这段代码是 Vue 3 中的 toRef 函数的实现,它用于将一个值、Ref 对象、获取器函数或响应式对象的属性转化为 Ref 对象。下面是这段代码的详细解释:

  • toRef 函数接受三个参数:
    • source: 要转化为 Ref 对象的源数据,可以是以下几种类型之一:
      • 一个对象(通常是一个响应式对象)。
      • 一个 Ref 对象。
      • 一个获取器函数(返回一个值或 Ref 对象的函数)。
      • 一个普通的 JavaScript 值。
    • key (可选): 当 source 是一个对象时,表示要获取的属性名。
    • defaultValue (可选): 用于指定属性不存在时的默认值。

接下来是函数的主要逻辑:

  1. 首先,通过 isRef 函数检查 source 是否已经是一个 Ref 对象。如果是,直接返回该 Ref 对象,无需再次包装。

  2. 然后,通过 isFunction 函数检查 source 是否是一个函数。如果是,说明 source 是一个获取器函数,将其包装成 GetterRefImpl 类的实例,这是一个只读的 Ref 对象,用于调用获取器函数并返回结果。

  3. 接下来,通过 isObject 函数检查 source 是否是一个对象,同时检查是否传入了 key 参数。如果是,表示要将对象的某个属性转化为 Ref 对象,调用 propertyToRef 函数来完成这个操作。因为并没有收集和触发依赖更新,所以不会对非响应式对象去改变视图。

  4. 如果以上条件都不满足,说明 source 是一个普通的 JavaScript 值,使用 ref 函数将其转化为一个 Ref 对象,然后返回。

总结一下,toRef 函数的作用是将不同类型的数据转化为 Ref 对象,以便在 Vue 3 中进行响应式的数据操作和追踪。这个函数的灵活性使得在组件内部可以轻松地操作不同类型的数据并确保其响应性。

class GetterRefImpl<T> {
  public readonly __v_isRef = true
  public readonly __v_isReadonly = true
  constructor(private readonly _getter: () => T) {}
  get value() {
    return this._getter()
  }
}

这段代码定义了一个名为 GetterRefImpl 的类,它用于创建一个只读的 Ref 对象,其内部的值是通过一个 getter 函数来获取的。以下是对这段代码的详细解释:

  • GetterRefImpl 类是一个泛型类,它接受一个类型参数 T,表示 Ref 内部值的类型。

  • 类中声明了两个只读属性:

    • __v_isRef: 表示该对象是一个 Ref 对象,用于标识这是一个 Ref 实例。
    • __v_isReadonly: 表示这个 Ref 对象是只读的,即其内部值不能被修改。
  • 构造函数 constructor 接受一个参数 _getter,这是一个函数类型,它没有参数,返回类型为 T。这个函数将用于获取 Ref 内部的值。

  • GetterRefImpl 类有一个 get 访问器属性 value,该属性的作用是返回通过 _getter 函数获取的值。这样,当访问 Refvalue 属性时,实际上是调用 _getter 函数来获取最新的值。

综上所述,GetterRefImpl 类的实例可以用来创建一个只读的 Ref 对象,其内部的值是通过传入的 getter 函数动态获取的。这种方式适用于那些需要计算或从其他地方获取值的场景,同时保持 Ref 对象的只读特性。这个类通常在 toRef 函数内部使用,用于将一个 getter 函数转化为 Ref 对象。

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

这段代码是 toRef 函数内部的一个辅助函数 propertyToRef 的实现,主要用于将一个对象的属性转化为 Ref 对象。以下是对这段代码的详细解释:

  • propertyToRef 函数接受三个参数:
    • source: 一个对象,通常是一个响应式对象。
    • key: 要获取的属性名。
    • defaultValue (可选): 用于指定属性不存在时的默认值。

函数的主要逻辑如下:

  1. 首先,通过 source[key] 获取对象 source 中指定属性 key 的值,并将其存储在变量 val 中。

  2. 接着,通过 isRef(val) 检查 val 是否已经是一个 Ref 对象。如果是,直接返回 val,无需再次包装为 Ref

  3. 如果 val 不是 Ref 对象,那么说明它是一个普通的 JavaScript 值。此时,创建一个新的 Ref 对象,使用 ObjectRefImpl 类的实例来包装该值,并传入 sourcekey 以及可选的 defaultValue 作为构造函数的参数。最后,将这个新的 Ref 对象返回。

总的来说,propertyToRef 的作用是将一个对象的属性转化为 Ref 对象,以便在 Vue 3 中对该属性进行响应式的数据操作和追踪。这个函数通常在 toRef 内部使用,用于处理对象属性的转化工作。

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}

  get value() {
    const val = this._object[this._key]
    return val === undefined ? this._defaultValue! : val
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }

  get dep(): Dep | undefined {
    return getDepFromReactive(toRaw(this._object), this._key)
  }
}

这段代码定义了一个类 ObjectRefImpl,它是用于处理对象属性引用的实现类。这个类的作用是创建一个包装对象属性的引用,使其能够被 Vue 3 的响应式系统进行跟踪和触发更新。

让我逐行解释这段代码:

  1. class ObjectRefImpl { ... }:这是类的定义,它接受两个泛型参数 TK,其中 T 表示对象的类型,K 表示对象属性的键。

  2. public readonly __v_isRef = true:这是类的属性,用于标识该对象是一个引用。

  3. 构造函数 constructor

    • private readonly _object: T:这是一个私有属性,表示要操作的对象。
    • private readonly _key: K:这是一个私有属性,表示要操作的对象属性的键。
    • private readonly _defaultValue?: T[K]:这是一个私有属性,表示属性的默认值,它是可选的。
  4. get value() 方法:这是一个 getter 方法,用于获取引用的值。它会从 _object 中根据 _key 获取属性的值,并在值为 undefined 时返回 _defaultValue

  5. set value(newVal) 方法:这是一个 setter 方法,用于设置引用的值。它将新的值 newVal 赋给 _object_key 属性。

  6. get dep(): Dep | undefined { ... } 方法:这是一个 getter 方法,用于获取与引用关联的依赖对象。它会通过 getDepFromReactive 函数从原始对象 _object 中获取与 _key 属性关联的依赖对象,以便在触发更新时使用。

总的来说,ObjectRefImpl 类用于包装对象属性的引用,使其具备响应式的特性。当访问引用的 value 属性时,它会返回对象属性的值,如果属性值为 undefined,则返回 _defaultValue。同时,它还提供了 dep 属性,用于获取与引用关联的依赖对象,以便在引用的属性发生变化时触发更新。这种引用对象通常在 Vue 3 的响应式系统中使用,以便跟踪和响应对象属性的变化。

toRefs 源码

export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = propertyToRef(object, key)
  }
  return ret
}

这段代码定义了一个名为 toRefs 的函数,它用于将一个响应式对象(reactive object)转换为一个包含原对象属性的引用(ref)的对象。这个函数在 Vue 3 中常用于处理响应式对象的属性,以便在模板中能够访问和修改这些属性。

以下是这段代码的详细解释:

  1. export function toRefs(object: T): ToRefs { ... }:这是函数的定义,它接受一个泛型参数 T,表示输入的对象类型,并返回一个 ToRefs 类型的结果。ToRefs 表示一个包含对象属性引用的对象。

  2. if (__DEV__ && !isProxy(object)) { ... }:这是一个条件判断语句,用于在开发模式下检查输入的对象是否是一个响应式对象。如果输入的对象不是响应式的,会发出警告提示。

  3. const ret: any = isArray(object) ? new Array(object.length) : {}:这一行代码初始化了一个变量 ret,用于存储转换后的引用对象。如果输入的对象是数组(通过 isArray 函数判断),则创建一个数组,否则创建一个空对象。

  4. for (const key in object) { ... }:这是一个 for...in 循环,用于遍历输入的对象的所有属性。

  5. ret[key] = propertyToRef(object, key):在循环中,对每个属性执行 propertyToRef 函数,将属性转换为引用,并将引用存储在 ret 对象中,属性名作为键。

  6. return ret:最后,函数返回包含属性引用的 ret 对象。

总结来说,toRefs 函数的主要作用是将一个响应式对象转换为一个包含原对象属性的引用对象。这个函数在使用 Vue 3 的响应式系统时非常有用,它使得在模板中能够方便地访问和修改响应式对象的属性,而不需要额外的 .value 访问方式。这有助于简化模板中的代码,使其更加清晰和易于维护。如果输入的对象不是响应式的,函数会在开发模式下发出警告,以提醒开发者检查输入是否正确。

toRaw 源码

export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}
export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}

这段代码定义了一个名为 toRaw 的函数,它用于获取一个对象的原始(非代理)版本。在 Vue 3 的响应式系统中,对象可以被代理以实现响应式,但有时需要访问原始对象而不是代理对象。这个函数的主要目的就是帮助你获取对象的原始版本。

以下是这段代码的详细解释:

  1. export function toRaw(observed: T): T { ... }:这是函数的定义,它接受一个泛型参数 T,表示输入的对象类型,并返回相同类型的原始对象。函数的目的是获取对象的原始版本。

  2. const raw = observed && (observed as Target)[ReactiveFlags.RAW]:这一行代码尝试从输入的 observed 对象中获取原始版本。首先,它通过逻辑与运算符 && 确保 observed 不为 nullundefined。然后,它使用 TypeScript 的类型断言 (observed as Target)observed 断言为 Target 类型,这是一个内部类型,用于存储代理对象的原始版本。最后,它尝试从代理对象的原始版本中获取 ReactiveFlags.RAW 属性,这是一个标识原始版本的属性。

  3. return raw ? toRaw(raw) : observed:这是返回语句,根据是否成功获取到原始版本来决定返回值。如果成功获取到原始版本(raw 存在),则递归调用 toRaw 函数,传入原始版本,以确保获取最终的原始版本。如果无法获取原始版本,则直接返回输入的 observed 对象,表示它本身就是原始对象或无法获取原始对象。

总结来说,toRaw 函数的主要作用是获取对象的原始版本。这在某些情况下很有用,例如当你需要访问对象的原始属性而不是代理属性时。这个函数可以用于在 Vue 3 中更底层的响应式操作中,以及处理一些特殊情况下的对象。但通常情况下,你可以直接使用代理对象的属性来访问和修改数据,而无需手动获取原始对象。

你可能感兴趣的:(Vue3,源码解析,vue.js,前端,javascript)