我们知道,一般用reactive
来定义一个响应式对象,ref
常用来定义一个响应式的原始值。上篇文章已经聊过了reactive
,知晓了如何通过Proxy
来对目标对象进行代理从而实现响应式,而非对象的这些原始值的响应式问题就交给ref
来解决了。
一、ref
和 shallowRef
的函数签名 ref
和shallowRef
各有三种重载,入参各不相同,都返回一个Ref
/ShallowRef
类型的值。通过createRef
函数创建一个响应式的值。和 reactive
相似,reactive
也是通过调用createReactiveObject
来创建一个响应式的对象。而createRef
创建并返回一个 RefImpl
实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export function ref<T extends object >( value : T ): [T] extends [Ref ] ? T : Ref <UnwrapRef <T>>; export function ref<T>(value : T): Ref <UnwrapRef <T>>;export function ref<T = any >(): Ref <T | undefined >;export function ref (value?: unknown ) { return createRef (value, false ); } export function shallowRef<T extends object >( value : T ): T extends Ref ? T : ShallowRef <T>; export function shallowRef<T>(value : T): ShallowRef <T>;export function shallowRef<T = any >(): ShallowRef <T | undefined >;export function shallowRef (value?: unknown ) { return createRef (value, true ); } function createRef (rawValue: unknown , shallow: boolean ) { if (isRef (rawValue)) { return rawValue; } return new RefImpl (rawValue, shallow); }
二、RefImpl
顺带一说,在ts
里,关键字class
既声明了一个值,也声明了一个ts
类型。RefImpl
算是ref
的核心内容了,构造器函数接收两个参数,value
是传入的原本的值,__v_isShallow
在上一篇讲reeactive
和readonly
的文章里,也有这个属性,用于区别深层/浅层。且isShallow()
函数也会利用这个属性来做判断。在这里两个作用,一是作为实例属性供isShallow
判断;而是根据传入时来值来判断是否是浅层的Ref
,因为函数shallowRef
也是创建一个RefImpl
实例。
可以看到,Ref
的响应式实现就比较简单了,用_value
属性来存储实际的值,用dep
属性存储依赖,用在class
的getter
里通过trackRefValue(this)
来收集依赖,在setter
里调用triggerRefValue(this, newVal)
。和vue2
里的实现相似,只是这里使用的 class
的getter
和setter
,而vue2
里使用的是属性描述符里的getter
和setter
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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); this ._value = __v_isShallow ? value : toReactive (value); } get value () { trackRefValue (this ); return this ._value ; } set value (newVal ) { const useDirectValue = this .__v_isShallow || isShallow (newVal) || isReadonly (newVal); newVal = useDirectValue ? newVal : toRaw (newVal); if (hasChanged (newVal, this ._rawValue )) { this ._rawValue = newVal; this ._value = useDirectValue ? newVal : toReactive (newVal); triggerRefValue (this , newVal); } } }
三、trackRefValue
trackRefValue
用于收集Ref
的依赖,接收一个RefBase
类型的值。在ref
函数中则是接收RefImpl
的实例。shouldTrack
是从effect
的模块引入的,用做暂停和恢复捕获依赖的标志;activeEffect
也是从effect
的模块引入,标记当前活跃的effect
。可以看到,内部调用trackEffects
函数来收集依赖,该函数来自effect
的模块,放在effect
的篇章里讲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export function trackRefValue (ref: RefBase<any > ) { if (shouldTrack && activeEffect) { ref = toRaw (ref); if (__DEV__) { trackEffects (ref.dep || (ref.dep = createDep ()), { target : ref, type : TrackOpTypes .GET , key : "value" , }); } else { trackEffects (ref.dep || (ref.dep = createDep ())); } } }
四、triggerRefValue
triggerRefValue
函数用于触发Ref
的响应式更新。triggerEffects
函数来自effect
的模块,在effect
的篇章里讲到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export function triggerRefValue (ref: RefBase<any >, newVal?: any ) { ref = toRaw (ref); if (ref.dep ) { if (__DEV__) { triggerEffects (ref.dep , { target : ref, type : TriggerOpTypes .SET , key : "value" , newValue : newVal, }); } else { triggerEffects (ref.dep ); } } }
五、customRef
和 CustomRefImpl
Vue3
还提供了自定义的Ref
,自己传入getter
和setter
,可以自由选择track
和trigger
的时机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class CustomRefImpl <T> { public dep?: Dep = undefined ; private readonly _get : ReturnType <CustomRefFactory <T>>["get" ]; private readonly _set : ReturnType <CustomRefFactory <T>>["set" ]; public readonly __v_isRef = true ; constructor (factory: CustomRefFactory<T> ) { const { get, set } = factory ( () => trackRefValue (this ), () => triggerRefValue (this ) ); this ._get = get; this ._set = set; } get value () { return this ._get (); } set value (newVal ) { this ._set (newVal); } } export function customRef<T>(factory : CustomRefFactory <T>): Ref <T> { return new CustomRefImpl (factory) as any ; }
六、toRef
、toRefs
和 ObjectRefImpl
在setup
函数中返参时,我们有时候想要对响应式对象的某个属性进行解构,往往是用到toRef
来创建一个ObjectRefImpl
实例。
可以看到,原来的响应式对象依然被这个ObjectRefImpl
实例通过_object
属性引用。而在getter
里面,会通过原本的响应式对象_object
来访问该值,因而依赖的收集是由原本的响应式对象_object
来进行的;同样的,在setter
里,也是通过引用原本的响应式对象_object
来达到赋值的操作,从而在_object
中触发更新。也就是说,ObjectRefImpl
不负责依赖收集和响应式更新,这些都是借由原本的响应式对象_object
完成的。
toRef
简要判断入参是否是一个Ref
,是则直接返回,否则返回一个新建的ObjectRefImpl
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 as T[K]) : val; } set value (newVal ) { this ._object [this ._key ] = newVal; } } export type ToRef <T> = IfAny <T, Ref <T>, [T] extends [Ref ] ? T : Ref <T>>;export function toRef<T extends object , K extends keyof T>( object : T, key : K ): ToRef <T[K]>; export function toRef<T extends object , K extends keyof T>( object : T, key : K, defaultValue : T[K] ): ToRef <Exclude <T[K], undefined >>; export function toRef<T extends object , K extends keyof T>( object : T, key : K, defaultValue?: T[K] ): ToRef <T[K]> { const val = object [key]; return isRef (val) ? val : (new ObjectRefImpl (object , key, defaultValue) as any ); }
而toRefs
则是对传入的对象/数组进行遍历并进行toRef
解构。
1 2 3 4 5 6 7 8 9 10 11 12 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] = toRef (object , key); } return ret; }