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
(可选): 用于指定属性不存在时的默认值。
接下来是函数的主要逻辑:
-
首先,通过
isRef
函数检查source
是否已经是一个Ref
对象。如果是,直接返回该Ref
对象,无需再次包装。 -
然后,通过
isFunction
函数检查source
是否是一个函数。如果是,说明source
是一个获取器函数,将其包装成GetterRefImpl
类的实例,这是一个只读的Ref
对象,用于调用获取器函数并返回结果。 -
接下来,通过
isObject
函数检查source
是否是一个对象,同时检查是否传入了key
参数。如果是,表示要将对象的某个属性转化为Ref
对象,调用propertyToRef
函数来完成这个操作。因为并没有收集和触发依赖更新,所以不会对非响应式对象去改变视图。 -
如果以上条件都不满足,说明
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
函数获取的值。这样,当访问Ref
的value
属性时,实际上是调用_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
(可选): 用于指定属性不存在时的默认值。
函数的主要逻辑如下:
-
首先,通过
source[key]
获取对象source
中指定属性key
的值,并将其存储在变量val
中。 -
接着,通过
isRef(val)
检查val
是否已经是一个Ref
对象。如果是,直接返回val
,无需再次包装为Ref
。 -
如果
val
不是Ref
对象,那么说明它是一个普通的 JavaScript 值。此时,创建一个新的Ref
对象,使用ObjectRefImpl
类的实例来包装该值,并传入source
、key
以及可选的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 的响应式系统进行跟踪和触发更新。
ObjectRefImpl
和RefImpl
的主要区别是 :RefImpl 会有收集和触发依赖更新的操作,而 ObjectRefImpl 并没有。
逐行解释这段代码:
-
class ObjectRefImpl<T extends object, K extends keyof T> { ... }
:这是类的定义,它接受两个泛型参数T
和K
,其中T
表示对象的类型,K
表示对象属性的键。 -
public readonly __v_isRef = true
:这是类的属性,用于标识该对象是一个引用。 -
构造函数
constructor
:private readonly _object: T
:这是一个私有属性,表示要操作的对象。private readonly _key: K
:这是一个私有属性,表示要操作的对象属性的键。private readonly _defaultValue?: T[K]
:这是一个私有属性,表示属性的默认值,它是可选的。
-
get value()
方法:这是一个 getter 方法,用于获取引用的值。它会从_object
中根据_key
获取属性的值,并在值为undefined
时返回_defaultValue
。 -
set value(newVal)
方法:这是一个 setter 方法,用于设置引用的值。它将新的值newVal
赋给_object
的_key
属性。 -
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 中常用于处理响应式对象的属性,以便在模板中能够访问和修改这些属性。
以下是这段代码的详细解释:
-
export function toRefs<T extends object>(object: T): ToRefs<T> { ... }
:这是函数的定义,它接受一个泛型参数T
,表示输入的对象类型,并返回一个ToRefs<T>
类型的结果。ToRefs<T>
表示一个包含对象属性引用的对象。 -
if (__DEV__ && !isProxy(object)) { ... }
:这是一个条件判断语句,用于在开发模式下检查输入的对象是否是一个响应式对象。如果输入的对象不是响应式的,会发出警告提示。 -
const ret: any = isArray(object) ? new Array(object.length) : {}
:这一行代码初始化了一个变量ret
,用于存储转换后的引用对象。如果输入的对象是数组(通过isArray
函数判断),则创建一个数组,否则创建一个空对象。 -
for (const key in object) { ... }
:这是一个for...in
循环,用于遍历输入的对象的所有属性。 -
ret[key] = propertyToRef(object, key)
:在循环中,对每个属性执行propertyToRef
函数,将属性转换为引用,并将引用存储在ret
对象中,属性名作为键。 -
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 的响应式系统中,对象可以被代理以实现响应式,但有时需要访问原始对象而不是代理对象。这个函数的主要目的就是帮助你获取对象的原始版本。
以下是这段代码的详细解释:
-
export function toRaw<T>(observed: T): T { ... }
:这是函数的定义,它接受一个泛型参数T
,表示输入的对象类型,并返回相同类型的原始对象。函数的目的是获取对象的原始版本。 -
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
:这一行代码尝试从输入的observed
对象中获取原始版本。首先,它通过逻辑与运算符&&
确保observed
不为null
或undefined
。然后,它使用 TypeScript 的类型断言(observed as Target)
将observed
断言为Target
类型,这是一个内部类型,用于存储代理对象的原始版本。最后,它尝试从代理对象的原始版本中获取ReactiveFlags.RAW
属性,这是一个标识原始版本的属性。 -
return raw ? toRaw(raw) : observed
:这是返回语句,根据是否成功获取到原始版本来决定返回值。如果成功获取到原始版本(raw
存在),则递归调用toRaw
函数,传入原始版本,以确保获取最终的原始版本。如果无法获取原始版本,则直接返回输入的observed
对象,表示它本身就是原始对象或无法获取原始对象。
总结来说,toRaw
函数的主要作用是获取对象的原始版本。这在某些情况下很有用,例如当你需要访问对象的原始属性而不是代理属性时。这个函数可以用于在 Vue 3 中更底层的响应式操作中,以及处理一些特殊情况下的对象。但通常情况下,你可以直接使用代理对象的属性来访问和修改数据,而无需手动获取原始对象。