SoFunction
Updated on 2025-04-07

Vue3 original value response scheme and interpretation of response loss problem

Preface

We learned that non-primitive values ​​are proxyed using proxy, but proxy cannot proxy the original values. In vue2, we cannot directly intercept the original value, but we put the data into data and proxy the entire data object, which naturally makes the original value in data also respond.

But vue3 does not have a data option, how to implement the original value response? This is about introducing what we want to learn.

Note: The following implementation principle is not the real source code, but can be regarded as the minimalist source code core implementation. The purpose of this is to put the source code in it, which is very numerous and complicated, and it is impossible to see the implementation of its core principle at a glance.

1. The introduction of ref

ref solves the problem that proxy cannot directly proxy the original value. Let's first look at the use of ref:

const name = ref('Little Black Sword')

How is ref implemented? In fact, it is to "wrap" the original value with the object. Let's take a look at the implementation of ref:

function ref(val){
    // Use object to wrap the original value    const wrapper = {
        value:val    
    }
    // Use reactive to turn objects into reactive data    return reactive(wrapper)
}

The implementation of ref is that simple.

ref mainly does these two things:

  • 1. Use the object to wrap the original value.
  • 2. Use reactive to turn the wrapping object into responsive data.

2. Implementation of isref

We use ref to create a responsive object, but how do we distinguish an object from a normal object or a ref object? So our isref appears.

Let's take a look at its use:

const name = ref('cj') (isRef(name)); // true

So what is its implementation principle? The main implementation is still within the ref API. Let’s take a look at the specific implementation code:

function ref(val){
    const wrapper = {
        value:val    
    }
    (warpper,'__v_isRef',{
        value:true    
    })
    return reactive(wrapper)
}

It turns out that it is to add an unenumerable and unwritable property to the package object within ref, and the value is true. In this way, we can check the property to determine whether it is ref.

function isRef(val) {
    return val.__v_isRef ?? false
}

3. Response loss

What is response loss? Response loss means that responsive data is no longer responding. Let's look at the code below:

const obj = reactive({foo:1,bar:2})
const {foo,bar} = obj
++    // fooWill not change,still 1

The above obj response has been lost, so re-rendering will not be triggered. Why is this happening? In fact, it is because of the use of structure assignment that the expansion operator will also invalidate it.

const obj = reactive({foo:1,bar:2})
const newObj = {...obj}
++   // Will not change,still 1

This is equivalent to redefining new data, and no longer the original response data, and naturally there is no responsiveness.

1. toRef appears

toRef is to solve the problem of response loss. Let's take a look at its implementation:

function toRef(obj,key) {
    const wrapper = {
        get value() {
            return obj[key]        
        },
        set value(val) {
            obj[key] = val
        }    
    }
    (wrapper,'__v_isRef',{
        value:true    
    })
    return wrapper
}

Pass in two parameters, the first is responsive data, and the second is a key of obj.

  • The first part is to set an object to declare. The get set of the value attribute is used to access the toRef value, and the attribute value corresponding to the incoming responsive data will be returned. Then the set of the value attribute is used to set the toRef value, and the attribute value corresponding to the responsive data will be updated with the new value set. In other words, the object returned by toRef is still the responsive data used.
  • The second part is used to set the returned data to be ref data. Because the data returned by toRef is similar to ref data, it is directly considered to be a ref data for the sake of uniformity.
  • The third part is to return the attribute object corresponding to the responsive data.

In this way, toRef solves the problem of response loss.

2. toRefs Join

toRefs is to deconstruct and responsive the entire responsive object. The implementation code is as follows:

function toRefs() {
    const ret = {}
    for (const key in obj) {
        ret[key] = toRef(obj,key)    
    }
    return ret
}

Use a for loop to convert properties one by one. Let's take a look at the use:

const obj = reactive({foo:1,bar:2})
const {foo,bar} = toRefs(obj)
++    // Become2It's

3. Other strange responsive loss situations

When the attribute is not a raw value, it can still respond after deconstruction

const obj = reactive({foo:{age:18},bar:2})
const {foo,bar} = obj
++    // It has become 2++       // barNo change,still1

why is that? The reason is actually very simple, because the non-primitive value assigned is the reference address, which means that the deconstructed variable actually points to the attributes of the original responsive data. The original value is a simple assignment and will not respond.

reactive Response data no longer responds after reassignment,ref Respond to data assignment
let obj1 = reactive({foo:1,bar:2})
let obj2 = ref({foo:1,bar:2})
 
// If obj1 and obj2 are displayed directly on the pageobj1 = {boo:2,far:3}    // Page obj1 or {foo:1,bar:2} = {boo:2,far:3}    // page obj2 Become {boo:2,far:3} It's  

What is the reason for this? The reactive reassignment response is lost, which means that the new object is reassigned, and it will naturally become ordinary data and no longer respond. The ref can still respond because ref performs set processing internally. The code is as follows:

function ref(val){
    const wrapper = {
        value:val
        set value(val) {    // isObject Here represents a method to determine whether it is a non-primitive value             value = isObject(val) === 'Object' ? reactive(val) : val       
        }
    }
    (warpper,'__v_isRef',{
        value:true    
    })
    return reactive(wrapper)
}

We understand that in fact, ref determines whether the set new value is a non-primitive value in set. If so, call reactive to turn it into reactive data.

4. Unref automatically removes ref

When we use ref responsive data, we feel that we always need .value to get the value, which increases the user's mental burden.

So can we access the value without accessing .value?

This is used without caring about whether a certain data is ref data, and whether it is necessary to obtain the value through the value attribute.

This is our unref to take action. unref implements the ability to automatically remove ref. Automatically remove ref means that if the read property is a ref, the value corresponding to the ref is directly returned.

Let's take a look at the implementation of unref:

function unref(target) {
    return new Proxy(target,{
        get(target,key,receiver) {
            const value = (target,key,receiver)
            return value.__v_isRef ?  : value        
        },
        set(target,key,newValue,receiver) {
            const value = target[key]
            if (value.__v_isRef) {
                 = newValue
                return true            
            }        
            return (target,key,newValue,receiver)
        }
    })
}

We found that unref uses Proxy to proxy the target object internally, receive an object as a parameter, and return the proxy object of that object. When we access unref data, the get capturer is triggered, and then the capturer determines whether the incoming object is a ref object. If so, it will directly return the .value value of ref. If not, it will directly return to the proxy object.

When setting a value for the proxy object returned by unref, the set capturer is triggered. If the proxy object is ref, the new value that needs to be set will be assigned to .value. If not, the assignment process will be performed directly.

Knowledge extension

When we use ref to create responsive data, display it in the template, why not use .value.

<script setup lang="ts">
import { ref } from 'vue'
const name = ref('Little Black Sword')
</script>
 
<template>
  <div>{{ name }}</div>
</template>

In fact, the reason is very simple. The ref responsive data declared in the component setup will be passed to the unref function for processing. Therefore, accessing the value of ref in the template does not need to be accessed through the value attribute.

The reactive we use actually has the automatic ref function. Take a look at the following example:

const count = ref(0)
const obj = reactive({conut})
 
    // 0    

We can see that it is a ref responsive data. After wrapping a layer of objects in the outer layer of count and passing them to reactive, you no longer need to access the value through the value attribute when accessing again.

It is precisely because reactive also realizes the ability to automatically remove ref.

Summarize

The above is personal experience. I hope you can give you a reference and I hope you can support me more.