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

  • refIn the functioncreateRef(value)
  • shallowRefIn the functioncreateRef(value, true)

Enter the createRef function, which takes two arguments

  • rawValueIs the original value passed in by calling the outer function
  • shallowshallowRefFunction is passed inside tocreateRefThe 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 yourrefThe statement is oneobjectIn this case, you need to modify the object, not the value inside the object

 const 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 wholerefreactiveThe function of “return” is the same, both return a responsive data, butrefNeed to pass through.valueProperty 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