preface
Another common and important VUE API ref implementation idea is that there is a container for storing values, there are two methods, one for calling trackEffect to collect dependencies, one for calling triggerEffect to trigger dependency updates, and there are many derivative apis, such as: ToRef toRefs etc
Source location in vue next3.2 / packages/reactivity/SRC/ref. Ts
Tool function
// If the user wraps the object with ref, convert it to a Reactive proxy object
constconvert = <T extends unknown>(val: T): T => isObject(val) ? Reactive (val) : val / / judge whether ref object export function isRef < T > (r: ref < T > | unknown) : r is ref < T > export function isRef (r: Any): r isRef {return Boolean(r && R.__v_isref === true)} export function toRaw<T>(observed: T): T { const raw = observed && (observed as Target)[ReactiveFlags.RAW] return raw ? toRaw(raw) : observed }Copy the code
A basic understanding of
The main functions in the code are implemented based on three classes, namely: RefImpl, CustomRefImpl, ObjectRefImpl
RefImpl: constructor of the REF API,
CustomRefImpl: Customizes the ref customRef constructor
ObjectRefImpl: The toRef constructor can be used to create a new REF for a property on the source responsive object. The REF can then be passed, maintaining a reactive connection to its source property.
The source code parsing
ref API
This method takes an internal value and returns a reactive, mutable ref object. The ref object has a single property.value pointing to an internal value.
Let’s see how it works
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) {// the logic is encapsulated increateRef 中
return createRef(value)}function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue} / /RefImplInstantiate aref
return new RefImpl(rawValue, shallow)}class RefImpl<T> {// Store a reactive valueprivate _value: T// Store raw dataprivate _rawValue: T/ / dependenciespublic dep? :Dep = undefined// indicate that he isref
public readonly __v_isRef = true// At initialization, yes_value _rawValueBut these two data cannot be directly retrieved and modified externally // there is a pairgetterandsetterTo intercept them.constructor(value: T, public readonly _shallow = false) {
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? value : convert(value)
}
get value() {
TrackEffects is called for dependency collection after some processing is done via trackRefValue
trackRefValue(this)
return this._value
}
set value(newVal) {
// Value may be a proxy object, which is usually not stored but rather raw data
newVal = this._shallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
// The raw data may be objects
this._value = this._shallow ? newVal : convert(newVal)
triggerRefValue(this, newVal)
}
}
}
Copy the code
RefImpl contains four important attributes: _value: fetch and modify data. _rawValue: stores raw data. When _value is modified, it will be modified as well. The side effect function is stored, and the triggering dependency is usually traversed through the collection. _v_isRef: This property is set to true to indicate that the object is of type REF
toRef
So this API is a variant of ref, which takes a value and turns it into a reactive value, and this API takes a property value of an object and turns it into a reactive property as a REF, so let’s look at the code implementation
export function toRef<T extends object.K extends keyof T> (
object: T,
key: K
) :ToRef<T[K] >{
// if the ref object returns, it is not instantiated
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)}class ObjectRefImpl<T extends object.K extends keyof T> {
// __v_isRef is true as the ref object identifier
public readonly __v_isRef = true
// Store the object store key
constructor(private readonly _object: T, private readonly _key: K) {}
The ref object itself does not store values
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
Copy the code
The resulting ref object does not store values, but a target object, and has an __v_isRef identifier as the ref identifier. However, fetching and modifying values is performed on the target object, and there is no dependency collection or dependency triggering operation. ToRefs is derived from this API.
toRefs
The idea is to walk through the target, get all the keys and values and call toRef and return the value to a structure object relative to it, and then return the object, but only if the target is a proxy object,
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.`)}// Generate a corresponding data structure based on the source data structure
const ret: any = isArray(object)?new Array(object.length) : {}
for (const key in object) {
// Store the converted data
ret[key] = toRef(object, key)
}
return ret
}
Copy the code
Custom Ref
The official documentation says this: Create a custom REF with explicit control over its dependency tracing and update triggering. It requires a factory function that takes track and trigger functions as arguments and should return an object with get and set.
class CustomRefImpl<T> {
publicdep? : 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>) {
// factory is the factory function passed in by the user
// Get and set are user-defined and pass dependency collection and dependency triggering functions
const { get, set } = factory(
() = > trackRefValue(this),
() = > triggerRefValue(this))// Store custom get and set
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
// customize Ref
export function customRef<T> (factory: CustomRefFactory<T>) :Ref<T> {
return new CustomRefImpl(factory) as any
}
Copy the code
Internally, a function needs to implement two methods and return. The function will receive methods that collect and trigger dependencies. Note that there are two points: 1. You need to manually call the collection and triggering dependency methods. 2. When updating the value, you will not check whether the value is changed or not, which may trigger some unnecessary calls and you need to check yourself
conclusion
In my opinion, ref module is the simplest in the whole responsive module. Compared with other modules, ref module can be easily understood without too complicated logic. Ref itself can be regarded as a container, which stores a series of attributes and a pair of update methods. In the transfer process, ref passes the address of the container, and reactive only passes the value returned by GET. Although toRefs can be converted to the value, it is better to use REF in the beginning