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 定義的對象,重新賦值后失去了響應式,變成了普通對象。

我們在官網(wǎng)可以看到:


官網(wǎng)描述,使用 ref 定義對象時,內(nèi)部引用了 reactive 函數(shù)處理深層次的響應式對象

那么問題來了:為什么 ref 調(diào)用 reactive 處理對象,為什么重新賦值后,沒有失去響應式,但是 reactive 卻失去了響應式?

過程

我們?nèi)タ纯丛创a咋寫的:

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; // get方法返回的是_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); // set方法調(diào)用 toReactive 方法
            triggerRefValue(this, newVal);
        }
    }
}
  1. 我們讀取 xxx.value 值的時候,getter 返回的是 xxx._value 的值,就是說,ref 定義的數(shù)據(jù),value 和 _value 的值是一樣的
    我們修改 xxx.value 值的時候,setter 調(diào)用 toReactive 方法
const toReactive = (value) => isObject(value) ? reactive(value) : value;
  1. toReactive 方法判斷是否是對象,是的話就調(diào)用 reactive 方法(印證了官網(wǎng)說的,ref 定義對象時,底層調(diào)用 reactive 方法實現(xiàn))
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 方法,先判斷數(shù)據(jù)是否是 “只讀” 的,不是就返回 createReactiveObject 方法處理后的數(shù)據(jù)

  2. createReactiveObject 方法將對象通過 proxy 處理為響應式數(shù)據(jù)

結論

ref 定義數(shù)據(jù)(包括對象)時,都會變成 RefImpl(Ref 引用對象) 類的實例,無論是修改還是重新賦值都會調(diào)用 setter,都會經(jīng)過 reactive 方法處理為響應式對象。
但是 reactive 定義數(shù)據(jù)(必須是對象),是直接調(diào)用 reactive 方法處理成響應式對象。如果重新賦值,就會丟失原來響應式對象的引用地址,變成一個新的引用地址,這個新的引用地址指向的對象是沒有經(jīng)過 reactive 方法處理的,所以是一個普通對象,而不是響應式對象

記在最后:

想到這個問題后,我就開始在網(wǎng)上搜索了好久,但是相關文章很少,有關的文章也沒看的太明白,所以最后決定去看源碼


源碼地址是 node_modules 下的 reactive 下的 dist 文件夾,里面有多個 js 文件,并且涉及到 ref 和 reactive 原理的方法基本一致,于是我用了最笨的方法,每個涉及的文件都做了debugger,最后發(fā)現(xiàn)是調(diào)用了 reactivity.esm-browser.js 文件的方法,然后不斷的 debugger 看完了大致的流程。
當我們遇到問題并且網(wǎng)上提供的幫助少之又少的情況下,查看源碼是我們解決問題的快捷方法。 小小記錄一下,與大家共勉。

PS: 我使用的是 webstorm 編輯器,可以通過 ctrl+點擊方法名 跳轉(zhuǎn)到方法創(chuàng)建處,使用 vscode 的話,需要安裝插件才有這個功能。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容