vue3, after the object is reassigned, reactive loses responsiveness, but ref does not

Ask a question

First the test code:

<template>
  <h1>{
    
    {
    
     foo.a }}</h1>
  <h1>{
    
    {
    
     bar.a }}</h1>
  <button @click="handleClick">点我</button>
</template>
<script setup>
import {
    
     ref, reactive, onMounted } from 'vue'

let foo = ref({
    
     a: 1, b: 2, c: 3 })
let bar = reactive({
    
     a: 4, b: 5, c: 6 })

const handleClick = () => {
    
    
  foo.value = {
    
    
    a: 11
  }
  bar = {
    
    
    a: 99
  }
  console.log('handleClick-->foo', foo)
  console.log('handleClick-->bar', bar)
}
onMounted(() => {
    
    
  console.log('onMounted-->foo', foo)
  console.log('onMounted-->bar', bar)
})
</script>

Output result after clicking:
Insert image description here
The object defined by ref does not lose its responsiveness after reassignment, but the object defined by reactive loses its responsiveness after reassignment and becomes a normal object.

We can see on the official website:
Insert image description here
The official website describes that when using ref to define an object, the reactive function is internally referenced to handle deep responsive objects.

So here comes the question: Why does ref call reactive to process the object? Why does it not lose responsiveness after reassignment, but reactive loses responsiveness?

process

Let’s take a look at how the source code is written:

class RefImpl {
    
    
    constructor(value, __v_isShallow) {
    
    
        this.__v_isShallow = __v_isShallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value);
        this._value = __v_isShallow ? value : toReactive(value);
    }
    get value() {
    
    
        trackRefValue(this);
        return this._value; // getter 返回的是_value的值
    }
    set value(newVal) {
    
    
        newVal = this.__v_isShallow ? newVal : toRaw(newVal);
        if (hasChanged(newVal, this._rawValue)) {
    
    
            this._rawValue = newVal;
            this._value = this.__v_isShallow ? newVal : toReactive(newVal); // setter 调用 toReactive 方法
            triggerRefValue(this, newVal);
        }
    }
}
  1. When we read the value of xxx.value, the getter returns the value of xxx._value. That is to say, the data defined by ref, the value of value and _value are the same. When we modify the value of
    xxx.value, the setter calls the toReactive method.
const toReactive = (value) => isObject(value) ? reactive(value) : value;
  1. The toReactive method determines whether it is an object, and if so, the reactive method is called (which confirms what the official website says, when ref defines an object, the bottom layer calls the reactive method)
function reactive(target) {
    
    
    // if trying to observe a readonly proxy, return the readonly version.
    if (isReadonly(target)) {
    
    
        return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
  1. reactive method, first determine whether the data is "read-only", if not, return the data processed by the createReactiveObject method

  2. The createReactiveObject method processes the object into responsive data through proxy

in conclusion

When ref defines data (including objects), it will become an instance of the RefImpl (Ref reference object) class. Whether it is modified or reassigned, the setter will be called, and it will be processed into a responsive object through the reactive method.
However, the reactive definition data (must be an object) is directly called by the reactive method to process it into a responsive object. If you reassign, the reference address of the original responsive object will be lost and become a new reference address. The object pointed to by this new reference address has not been processed by the reactive method, so it is an ordinary object, not a responsive object.

Solution

  1. Use ref() to define responsive variables (this method is recommended, although you need to write more .value. But it can avoid many pitfalls)
  2. Define one more variable let obj = reactive({ targetObj: {a:1,b:2}}) outside the target object. When reassigning obj.targetObj = {c:1,d:2}, the response will not be lost. formula, but you also need to write an extra prefix of obj. (recommended method 1)

Note at the end:

After thinking about this problem, I started searching on the Internet for a long time, but there were very few related articles, and the related articles were not very clear, so I finally decided to look at the source code. The source code address is the dist folder under reactive under node_modules
Insert image description here
. There are multiple js files, and the methods involving the ref and reactive principles are basically the same, so I used the stupidest method. I did a debugger on each file involved, and finally found that the reactivity.esm-browser.js file was called. method, and then continue to use the debugger to read the general process.
When we encounter a problem and there is little help available online, viewing the source code is a quick way for us to solve the problem. I will record it briefly and share it with everyone.

PS: I am using the webstorm editor. You can jump to the method creation location by ctrl+clicking on the method name. If you use vscode, you need to install a plug-in to have this function.

Guess you like

Origin blog.csdn.net/weixin_46683645/article/details/125934579