Ref type definition
The Ref interface defines the type signature returned by the Ref function, the value attribute holds the original value of the Ref, the [RefSymbol] is the only internally defined symbol for type differentiation, and the _shallow symbol indicates whether the Ref is a shallow Ref.
declare const RefSymbol: unique symbol
export interface Ref<T = any> {
value: T
/** * Type differentiator only. * We need this to be in public d.ts but don't want it to show up in IDE * autocomplete, so we use a private Symbol instead. */
[RefSymbol]: true
/ * * *@internal* /_shallow? :boolean
}
Copy the code
UnwrapRef
UnwrapRef is the type declaration used to UnwrapRef. Infer the package type and then UnwrapRefSimple can be used to further unwrap. UnwrapRefSimple defines the following rules:
Function
Type, collection type, base type,Ref
The type,RefUnwrapBailTypes
Returns directly, because there is no nesting unnesting involved;- Arrays and objects, yes
ref
The transformation is performed on the objectreactive
So you have to do deep unpacking.
export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
type UnwrapRefSimple<T> = T extends
| Function // a function
| CollectionTypes // Map|Set|WeakSet|WeakMap
| BaseTypes // string | boolean | number
| Ref // a Ref
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
? T
: T extends Array<any>? { [Kin keyof T]: UnwrapRefSimple<T[K]> }
: T extends object
? UnwrappedObject<T>
: T
Copy the code
Because in keyof an Object structure results in the loss of symbol as the signature attribute, causing an Object to lose some of the basic constraints of the Object type, it is necessary to manually access and add (these attribute signatures are already hard-coded into Object) :
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>
// Extract all known symbols from an object
// when unwrapping Object the symbols are not `in keyof`, this should cover all the
// known symbols
type SymbolExtract<T> =
(T extends{[Symbol.asyncIterator]: infer V }? {[Symbol.asyncIterator]: V } : {}) &
(T extends{[Symbol.hasInstance]: infer V } ? {[Symbol.hasInstance]: V } : {}) &
(T extends{[Symbol.isConcatSpreadable]: infer V } ? {[Symbol.isConcatSpreadable]: V } : {}) &
(T extends{[Symbol.iterator]: infer V } ? {[Symbol.iterator]: V } : {}) &
(T extends{[Symbol.match]: infer V } ? {[Symbol.match]: V } : {}) &
(T extends{[Symbol.matchAll]: infer V } ? {[Symbol.matchAll]: V } : {}) &
(T extends{[Symbol.replace]: infer V } ? {[Symbol.replace]: V } : {}) &
(T extends{[Symbol.search]: infer V } ? {[Symbol.search]: V } : {}) &
(T extends{[Symbol.species]: infer V } ? {[Symbol.species]: V } : {}) &
(T extends{[Symbol.split]: infer V } ? {[Symbol.split]: V } : {}) &
(T extends{[Symbol.toPrimitive]: infer V } ? {[Symbol.toPrimitive]: V }: {}) &
(T extends{[Symbol.toStringTag]: infer V } ? {[Symbol.toStringTag]: V } : {}) &
(T extends{[Symbol.unscopables]: infer V } ? {[Symbol.unscopables]: V } : {})
Copy the code
ToRef
This is mainly used for Union types that contain Ref. We want Union types to not produce type distribution:
export type ToRef<T> = [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export type ToRefs<T = any> = {
// #2687: somehow using ToRef<T[K]> here turns the resulting type into
// a union of multiple Ref<*> types instead of a single Ref<* | *> type.
[K in keyof T]: T[K] extends Ref ? T[K] : Ref<UnwrapRef<T[K]>>
}
Copy the code
ref
To see ref function implementation, the first three rows are function overloading, in addition to not pass parameters will directly return a ref < T | undefined >, otherwise the ref returned is ref < UnwrapRef < T > > type of ref first unpack again packet, including ToRef organized distribution conditions, It has separate instructions:
export function ref<T extends object> (value: T) :ToRef<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)}Copy the code
createRef
Ref createRef creates a ref type by calling a rawValue. If rawValue is already ref type, it returns a ref type by RefImpl.
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
Copy the code
RefTmpl
RefImpl is the actual Ref type constructor and contains two private attributes:
_value
: Stores incomingrawValue
;__v_isRef
:Ref
Identifier, used for judgment;
The constructor takes two arguments (_rawValue) and defines whether _shallow is a shallow ref. If not, the shallow ref will be reactive when the object is passed in:
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
// ...
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
Copy the code
Then there is the body of the ref reactive realized, review the reactive object structure response type, add all attributes of the object by proxy get&set interceptors, and in the interceptor to do track | trigger tracking effect.
The Ref object has only one responsive attribute, value, so RefImpl adds get&set to this attribute directly:
get
: calltrack
The function collects side effects as themselvestarget
Incoming (this
);set
: first, through thehasChanged
(In fact= = =
The extraNaN
The logic of judgmentrawValue
If there is change, there is change to update_value
Values, update rules andconstructor
Consistent. The final will betrigger
Trigger all side effects functions.
class RefImpl<T> {
// ...
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any.oldValue: any) :boolean= >value ! == oldValue && (value === value || oldValue === oldValue)Copy the code
Examine the test case for ref in ref.spec. The previous reason for converting passed objects is to allow nested attributes to respond:
it('should make nested properties reactive'.() = > {
const a = ref({
count: 1
})
let dummy
effect(() = > {
dummy = a.value.count
})
expect(dummy).toBe(1)
a.value.count = 2
expect(dummy).toBe(2)})Copy the code
customRef
CustomRef is used to define a ref that explicitly controls the dependency trace and trigger response, takes a factory function with track for the trace and trigger for the response, and returns an object with get and set attributes
export function customRef<T> (factory: CustomRefFactory<T>) :Ref<T> {
return new CustomRefImpl(factory) as any
}
export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) = > {
get: () = > T
set: (value: T) = > void
}
Copy the code
CustomRef via CustomRefImpl inserts track and trigger without much ado:
class CustomRefImpl<T> {
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(
() = > track(this, TrackOpTypes.GET, 'value'),
() = > trigger(this, TriggerOpTypes.SET, 'value'))this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
Copy the code
shallowRef
ShallowRef prevents it from converting internal elements to reactive:
export function shallowRef<T extends object> (
value: T
) :T extends Ref ? T : Ref<T>
export function shallowRef<T> (value: T) :Ref<T>
export function shallowRef<T = any> () :Ref<T | undefined>
export function shallowRef(value? : unknown) {
return createRef(value, true)}Copy the code
isRef
IsRef determines whether the parameter is of type REF and does type narrowing with type predicates:
export function isRef<T> (r: Ref<T> | unknown) :r is Ref<T>
export function isRef(r: any) :r is Ref {
return Boolean(r && r.__v_isRef === true)}Copy the code
toRef
ToRef can be used to create a ref for a property of an object. This ref can be passed and remains responsive:
const state = reactive({
foo: 1.bar: 2,})const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) / / 2
state.foo++
console.log(fooRef.value) / / 3
Copy the code
The function is defined as follows, internally calling ObjectRefImpl:
export function toRef<T extends object.K extends keyof T> (
object: T,
key: K
) :ToRef<T[K] >{
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)}Copy the code
ObjectRefImpl is implemented in a very simple way. It caches references and keys to incoming objects and then proxies its value to the source object, modifying the same reference regardless of whether the ref or the source object is modified.
If the original object is a Reactive object, set/ GET triggers its internal proxy so it remains responsive.
class ObjectRefImpl<T extends object.K extends keyof T> {
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
Copy the code
toRefs
Convert a reactive object/plain object to a plain object with the value ref. Each property of the plain object is a ref, corresponding to the property of the reactive object.
const state = reactive({
foo: 1.bar: 2,})const stateAsRefs = toRefs(state)
/* stateAsRefs: {foo: Ref
, bar: Ref
}
Copy the code
Construct a new object or array and call toRef internally to iterate over all key copies:
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
}
Copy the code
unref
A very simple syntactic sugar:
export function unref<T> (ref: T) :T extends Ref<infer V>?V : T {
return isRef(ref) ? (ref.value as any) : ref
}
Copy the code