遞歸監聽
默認情況下,Vue3 中的 ref 和 reactive 都是遞歸監聽的(層級深的對象),即能實時監聽對象的底層變化。
例如,在 ref 中
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let msg = ref({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
};
return {
msg,
c
};
}
}
</script>

點擊 button

reactive遞歸
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let msg = reactive({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.a.b.c = 'C';
msg.e.f = 'F';
msg.g = 'G';
};
return {
msg,
c
};
}
}
</script>
在 reactive 中也是類似的。總之,就是只要我們對 ref 和 reactive 中的內容進行更改,都是能察覺到並且進行雙向數據綁定的。
在默認情況下,遞歸監聽肯定是好的,它讓數據的變化能被實時監測到。然而它也帶來了性能消耗的問題。
Vue3 提供了 shallow 方案,以防止進行遞歸式的監聽。
非遞歸監聽
1.遞歸監聽存在的問題 如果數據量比較大, 非常消耗性能 2.非遞歸監聽 shallowRef / shallowReactive 3.如何觸發非遞歸監聽屬性更新界面? 如果是shallowRef類型數據, 可以通過triggerRef來觸發
4.應用場景 一般情況下我們使用 ref和reactive即可 只有在需要監聽的數據量比較大的時候, 我們才使用shallowRef/shallowReactive
#shallow
#shallowRef
shallow 式的創建 ref 需要使用一個新的 api,shallowRef。
嘗試用 shallowRef 對我們一開始的示例進行改造。
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
};
return {
msg,
c
};
}
}
</script>
此時我們再點擊 button ,會發現控制台提示了數據的改變,但並沒有實現對界面的數據綁定。

shallow類型的數據,只會監聽最外層的數據的變化,才會引起視圖層的變化,此時shaollowRef類型最外層的數據是value(並不是.g),所以只有在直接改變 msg.value 的時候才會產生監測,
例如
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
// msg.value.a.b.c = 'C';
// msg.value.e.f = 'F';
// msg.value.g = 'G';
msg.value = {
a: {
b: {
c: 'C'
}
},
e: {
f: 'F'
},
g: 'G'
}
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
此時最外層value的數據改變(被監聽到),視圖ui才會發生變化

#triggerRef
除此之外,對於 shallow 過的 ref 對象,我們還可以手動去觸發 ref 的變化監聽來實現界面的改變。
使用的 api 是 triggerRef
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
triggerRef(msg);
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
此時不需要更改最外層value的數據,內層的數據發生的變化,也同樣被監聽到,觸發視圖跟新
#shallowReactive
同樣的,我們還有 shallowReactive 來實現類似 shallowRef 的功能。
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowReactive({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.a.b.c = 'C';
msg.e.f = 'F';
msg.g = 'G';
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
但如果你有進行實踐的話會發現,這段代碼仍然會允許你在點擊 button 的時候對界面 UI 進行改變。

原因很簡單,就是我在上文提到的,shallow 會監測最外層的變化而請求更新視圖層,之前在 shallowRef 中的最外層是 value ,所以我們只能改變整個 value 值來提醒變化,而這里 shallowReactive 的最外層變成了 a、 e、g,而上面的代碼改變了 msg.g,所以引起了變化,如果我們將函數 c 的代碼改成
msg.a.b.c = 'C'; msg.e.f = 'F'; // msg.g = 'G';
這將不會引起 視圖層 的變化。(因為最外層msg.g數據並沒有發生變化,不會觸發視圖層的更新)

#triggerReactive
在 shallowReactive 中,並沒有提供 trigger 方案來主動喚醒監測變化。
#總結
本質上,shallowRef 是特殊的 shallowReactive,而 ref 是特殊的 reactive。明白了這一點,理解兩者的異同就會簡單許多。
// ref -> reactive // ref(10) -> reactive({value:10}) // shallowRef -> shallowReactive // shallowRef(10) -> shallowReactive({value: 10}) // 所以如果是通過shallowRef創建的數據, 它監聽的是.value的變化 // 因為底層本質上value才是第一層
