vue3,对象重新赋值后,reactive 丢失响应式,而 ref 不会

提出问题

先上测试代码:

<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>

点击后的输出结果:
在这里插入图片描述
ref 定义的对象,重新赋值后没有失去响应式,但是 reactive 定义的对象,重新赋值后失去了响应式,变成了普通对象。

我们在官网可以看到:
在这里插入图片描述
官网描述,使用 ref 定义对象时,内部引用了 reactive 函数处理深层次的响应式对象

那么问题来了:为什么 ref 调用 reactive 处理对象,为什么重新赋值后,没有失去响应式,但是 reactive 却失去了响应式?

过程

我们去看看源码咋写的:

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. 我们读取 xxx.value 值的时候,getter 返回的是 xxx._value 的值,就是说,ref 定义的数据,value 和 _value 的值是一样的
    我们修改 xxx.value 值的时候,setter 调用 toReactive 方法
const toReactive = (value) => isObject(value) ? reactive(value) : value;
  1. toReactive 方法判断是否是对象,是的话就调用 reactive 方法(印证了官网说的,ref 定义对象时,底层调用 reactive 方法实现)
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 方法,先判断数据是否是 “只读” 的,不是就返回 createReactiveObject 方法处理后的数据

  2. createReactiveObject 方法将对象通过 proxy 处理为响应式数据

结论

ref 定义数据(包括对象)时,都会变成 RefImpl(Ref 引用对象) 类的实例,无论是修改还是重新赋值都会调用 setter,都会经过 reactive 方法处理为响应式对象。
但是 reactive 定义数据(必须是对象),是直接调用 reactive 方法处理成响应式对象。如果重新赋值,就会丢失原来响应式对象的引用地址,变成一个新的引用地址,这个新的引用地址指向的对象是没有经过 reactive 方法处理的,所以是一个普通对象,而不是响应式对象

解决方法

  1. 使用 ref() 定义响应式变量 (推荐此方法,虽然需要多写 .value。但是可以避免很多坑)
  2. 在目标对象外边再多定义一个变量 let obj = reactive({ targetObj: {a:1,b:2}}),重新赋值时 obj.targetObj = {c:1,d:2},不会丢失响应式,但是也是要多写一个 obj. 的前缀 (推荐方法一)

记在最后:

想到这个问题后,我就开始在网上搜索了好久,但是相关文章很少,有关的文章也没看的太明白,所以最后决定去看源码
在这里插入图片描述
源码地址是 node_modules 下的 reactive 下的 dist 文件夹,里面有多个 js 文件,并且涉及到 ref 和 reactive 原理的方法基本一致,于是我用了最笨的方法,每个涉及的文件都做了debugger,最后发现是调用了 reactivity.esm-browser.js 文件的方法,然后不断的 debugger 看完了大致的流程。
当我们遇到问题并且网上提供的帮助少之又少的情况下,查看源码是我们解决问题的快捷方法。 小小记录一下,与大家共勉。

PS: 我使用的是 webstorm 编辑器,可以通过 ctrl+点击方法名 跳转到方法创建处,使用 vscode 的话,需要安装插件才有这个功能。

猜你喜欢

转载自blog.csdn.net/weixin_46683645/article/details/125934579