Preface: The previous article recorded the difference between vue3 combinative API and VUe2 optional API, this article is mainly on the combinative API setup using notes and ref source code interpretation learning

setup

The Setup component option is executed before the component is created, once the props is resolved, and is used as an entry point to the composite API.

Note:

  • setupThe execution time of the function is inbeforeCreateandcreatedbefore
  • Due to thesetupThe execution time is atcreatedBefore, so the component has just been created, anddataandmethodsIt’s not initialized yet, so it can’tsetupThe use ofdataandmethods
  • setupIn thethisPoint to theundefined
  • setupIt can only be synchronous, not asynchronous

Ref source code

The most important thing ref does is it provides a set of ref types, so let’s look at what it is.

// Generate a unique key, add descriptor 'refSymbol' in development environment
export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)

// Declare the Ref interface
export interface Ref<T = any> {
  // Use this unique key as a descriptor for the Ref interface, and let isRef do the type judgment
  [refSymbol]: true
  // value is the place where the real data is stored. I'll explain the type UnwrapNestedRefs separately later
  value: UnwrapNestedRefs<T>
}

// check if it is Ref data
/ / for is the key word, if not familiar with, see: http://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates
export function isRef(v: any) :v is Ref {
  return v ? v[refSymbol] === true : false
}

// See explanation below
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
Copy the code

To understand UnwrapNestedRefs and UnwrapRef, we must first understand INFER from TS. If you don’t know this before, please read the relevant documentation first. After reading the document, I suggest you go to Google to see some cases to deepen your impression. Look at the source code again:

// Reference data types should not continue recursively
type BailTypes =
  | Function
  | Map<any.any>
  | Set<any>
  | WeakMap<any.any>
  | WeakSet<any>

// Get the type of nested data recursively
// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
  // If it is ref, continue unpacking
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  // If it is an array, loop unpacking
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  // If it is an object, iterate over the unnesting
  object: { [K in keyof T]: UnwrapRef<T[K]> }
  // Otherwise, stop unpacking
  stop: T
}[T extends Ref
  ? 'ref'
  : T extends Array<any>?'array'
    : T extends BailTypes
      ? 'stop' // bail out on types that shouldn't be unwrapped
      : T extends object ? 'object' : 'stop']

// Declare type alias: UnwrapNestedRefs
// It is of type like this: if the type already inherits from Ref, there is no need to unpack, otherwise it might be nested Ref, go recursively unpack
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

Copy the code

A Ref is a data structure that has a key for Symbol as a type identifier and a value for storing data. This data can be of any type, but not of a nested Ref type. Array

or {[key]: Ref}. But oddly enough, Ref is ok.

In addition, Map, Set, WeakMap, WeakSet also do not support unnesting. Note The value of the Ref data may also be Map

. Ref type data is a reactive type of data. Then we look at the implementation:

// From @vue/shared to determine if a data is an object
// Record
      
        represents any type of key, any type of value
      ,>
Val is object Take a look at this answer: https://stackoverflow.com/questions/52245366/in-typescript-is-there-a-difference-between-types-object-and-recordany-any
export constisObject = (val: any): val is Record<any, any> => val ! = =null && typeof val === 'object'

// If the value passed is an object (including array /Map/Set/WeakMap/WeakSet), use Reactive, otherwise return the original data
// From the previous article, reactive is to transform our data into responsive data
const convert = (val: any): any= > (isObject(val) ? reactive(val) : val)

export function ref<T> (raw: T) :Ref<T> {
  // Convert data
  raw = convert(raw)
  const v = {
    [refSymbol]: true.get value() {
      // The track code is in effect, so you can guess that this is the way to collect the dependency of the listener function.
      track(v, OperationTypes.GET, ' ')
      // Return the converted data
      return raw
    },
    set value(newVal) {
      // Convert the value to reactive data and assign the value to raw
      raw = convert(newVal)
      // trigger is the method that triggers the execution of the listener function
      trigger(v, OperationTypes.SET, ' ')}}return v as Ref<T>
}

Copy the code

The hardest thing to understand is this ref function. We see that get/set is also defined, but there are no proxy-related operations. From the previous information we know that Reactive builds reactive data, but the parameters must be objects. But when ref’s input parameter is an object, reactive also needs to do the transformation. So what is the purpose of ref? Why do we need it?

Refer to the official vuE3 documentation for a clearer understanding.

For primitive data types, the reference to the original data is lost when the function is passed or the object is deconstructed. In other words, we cannot make the primitive data type, or the deconstructed variable (if its value is also a primitive data type), responsive data. We can never make basic data like A or X responsive data, and proxies can’t hijack basic data.

But sometimes we just want a number or a string to be reactive, or we just want to write it destructively. So what to do? You can only create an object, the Ref data in the source code, store the original data in the Ref attribute value, and return a reference to it to the consumer. Since the object is created by ourselves, there is no need to use Proxy to make Proxy. We can directly hijack the get/set of the value, which is the origin of the ref function and ref type. However, ref alone does not solve the problem of object deconstruction. It simply holds the basic data in the value of an object to achieve data responsiveness. Another function is needed for object deconstruction: toRefs.

export function toRefs<T extends object> (
  object: T
) :{ [K in keyof T]: Ref<T[K]> } {
  const ret: any = {}
  // Iterate over all keys of the object and convert their values to Ref data
  for (const key in object) {
    ret[key] = toProxyRef(object, key)
  }
  return ret
}
function toProxyRef<T extends object.K extends keyof T> (
  object: T,
  key: K
) :Ref<T[K] >{
  const v = {
    [refSymbol]: true.get value() {
      // Note that track is not used here
      return object[key]
    },
    set value(newVal) {
      // Note that trigger is not used here
      object[key] = newVal
    }
  }
  return v as Ref<T[K]>
}

Copy the code

By iterating through the object, each attribute value is converted to Ref data, so that the deconstructed Ref data remains, naturally preserving the reference to reactive data. ToRefs does not inject track or trigger into get/set. If you pass a normal object to toRefs, it will not return a response. To be responsive, you must pass an object that is already returned by Reactive.

The last

Source code order is adjusted, easy to understand and read, to here ref source code has been read, if you feel confused can read several times, anyway, I also feel a little confused.