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:
setup
The execution time of the function is inbeforeCreate
andcreated
before- Due to the
setup
The execution time is atcreated
Before, so the component has just been created, anddata
andmethods
It’s not initialized yet, so it can’tsetup
The use ofdata
andmethods
setup
In thethis
Point to theundefined
setup
It 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.