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
和 shallow
。rawValue
是传入的值,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
函数接收三个参数:source
,key
和 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