遞歸監聽
默認情況下,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才是第一層