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:

  • FunctionType, collection type, base type,RefThe type,RefUnwrapBailTypesReturns directly, because there is no nesting unnesting involved;
  • Arrays and objects, yesrefThe transformation is performed on the objectreactiveSo 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:RefIdentifier, 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: calltrackThe function collects side effects as themselvestargetIncoming (this);
  • set: first, through thehasChanged(In fact= = =The extraNaNThe logic of judgmentrawValueIf there is change, there is change to update_valueValues, update rules andconstructorConsistent. The final will betriggerTrigger 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