preface
Prepare a demo
. <script src=".. /dist/vue.global.js"></script> <script> const { computed, ref, watch, watchEffect, createApp, reactive } = Vue debugger const refVal = ref(0) debugger </script> ...Copy the code
ref
According to the source code of the debug into the ref, createRef is called inside. There are two places in the current file where createRef is called
ref
In the functioncreateRef(value)
shallowRef
In the functioncreateRef(value, true)
Enter the createRef function, which takes two arguments
rawValue
Is the original value passed in by calling the outer functionshallow
是shallowRef
Function is passed inside tocreateRef
The value of the
We call the isRef function to determine if the rawValue is of type ref. If it is, we return rawValue. Otherwise, return the instance of RefImpl.
Let’s look at the RefImpl class
class RefImpl<T> { private _value: T public readonly __v_isRef = true constructor(private _rawValue: T, public readonly _shallow: boolean) { this._value = _shallow ? _rawValue : convert(_rawValue) } get value() { // ... } set value(newVal) { // ... }}Copy the code
This._value is assigned to constructor. If _shallow is true, s indicates a shallow response object called via shallowRef. In this case, _rawValue is assigned directly. If _rawValue is an object, then _rawValue is converted to a reactive object. If _rawValue is an object, then _rawValue is converted to a reactive object. The final result is assigned to this._value.
Finally, you can see that the demo defines the values of refVal as follows
{_rawValue: 0, _shallow: false, __v_isRef: true, _value: 0, value: 0}
Copy the code
At this point, according to our demo, our debugging is over. But you can see that the RefImpl class also defines the get and set functions. Now we need to change our demo.
get
The get function in the RefImpl class is as follows
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
Copy the code
Add a code to the demo to get its value property so it can go into the get function
console.log(refVal.value)
Copy the code
The get function does two things. One is to call the track function to collect dependencies, as shown in the reactive source article. Another is return this._value.
But does calling track really collect dependencies here, because track starts with a judgment like this:
if (! shouldTrack || activeEffect === undefined) { return }Copy the code
The answer to this question is no, because activeEffect === undefined. That is, currently only calls to the watchEffect function will collect dependencies.
set
The set function in the RefImpl class is as follows
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)
}
}
Copy the code
As with the get function, the existing demo is still not sufficient for our needs. We need to add an operation that triggers the set function
refObj.value = 321
Copy the code
Here’s a note of caution if your
ref
The statement is oneobject
In this case, you need to modify the object, not the value inside the objectconst refObj = ref({ a: 1, b: 2 }) Copy the code
Refobj.value = 321 instead of refobj.value. A = 3
When we enter the set function, we see that all operations are performed after the original value of newValue is compared to the original value of oldValue.
-
The toRaw function gets the value of the original value passed in
-
The hasChanged function compares whether two parameters are equal. There’s a piece of code in this method that needs to be noticed
(value === value || oldValue === oldValue) Copy the code
NaN === NaN is not equal to NaN.
_rawValue is used to store the raw value that has not been processed. If this._shallow is true, _value is the same as rawValue. If newValue is an object, it returns a reactive object that was processed by the reactive function reactive. In this case, _rawValue and _value are not the same only for objects, but for everything else.
The trigger function is then called to trigger the dependency.
This is the source code for all the refs.
conclusion
- As a whole
ref
和reactive
The function of “return” is the same, both return a responsive data, butref
Need to pass through.value
Property to access.
toRefs
This is what I’m used to when working with comcomposition API
. const data = reactive({a: 1, b: 2}) return { ... toRefs(data) }Copy the code
This allows you to use the values defined in data directly in the template; otherwise, you need data.xxx to use one of the values. Next, take a look at the source code for toRefs.
You can see from the beginning of toRefs that the parameter that toRefs receives is a responsive object, and if it is not a responsive object there will be a warning in the development environment.
If object is an array, create an empty array with the same length as object. If object is an empty array, create an empty object and name it ret.
We then traverse the object, calling toRef to convert the corresponding value of each key into ref and assign the corresponding value to ret. The following code
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
Copy the code
Finally ret return.
Now let’s look at what the toRef function does.
The toRef function determines whether the value corresponding to the key in object isRef by isRef function. If yes, it takes the pair value; otherwise, it encapsulates it as ref by ObjectRefImpl class, and finally returns it.
That’s all the code for toRefs.
extension
Q: Why does an object wrapped in reactive lose its response when it is directly assigned a value, but an object wrapped in ref does not lose its response when it is assigned a value?
A: Because it internally listens for the corresponding value, the set logic of value is triggered when a value is assigned to the REF object, thus enabling a responsive update.
conclusion
ToRefs converts every item in an object or array to a ref type, which we can access as a.value. The reason why we don’t need to use.value in the template is because it was omitted when the template was compiled.
Refer to the link
Vue3.0 (4) ref source analysis and toRefs