
提出問題
先上測試源碼:
<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);
}
}
}
- 我們讀取 xxx.value 值的時候,getter 返回的是 xxx._value 的值,就是說,ref 定義的數(shù)據(jù),value 和 _value 的值是一樣的
我們修改 xxx.value 值的時候,setter 調(diào)用 toReactive 方法
const toReactive = (value) => isObject(value) ? reactive(value) : value;
- 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);
}
reactive 方法,先判斷數(shù)據(jù)是否是 “只讀” 的,不是就返回 createReactiveObject 方法處理后的數(shù)據(jù)
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 的話,需要安裝插件才有這個功能。