vue3中遞歸監聽和非遞歸監聽(系列七)


遞歸監聽

默認情況下,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>
 

0JY0MT.png

點擊 button

0JYUGq.png

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來觸發
    注意點: 如果是通過shallowRef創建數據,
     那么Vue監聽的是.value的變化, 並不是第一層的變化
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 ,會發現控制台提示了數據的改變,但並沒有實現對界面的數據綁定。

0JYaR0.png

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才會發生變化

 

0JYdzV.png

#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 進行改變。

0JYBsU.png

原因很簡單,就是我在上文提到的,shallow 會監測最外層的變化而請求更新視圖層,之前在 shallowRef 中的最外層是 value ,所以我們只能改變整個 value 值來提醒變化,而這里 shallowReactive 的最外層變成了 a、 eg而上面的代碼改變了 msg.g,所以引起了變化,如果我們將函數 c 的代碼改成

msg.a.b.c = 'C';
msg.e.f = 'F';
// msg.g = 'G';
 

這將不會引起 視圖層 的變化。(因為最外層msg.g數據並沒有發生變化,不會觸發視圖層的更新)

0JYsZ4.png

#triggerReactive

在 shallowReactive 中,並沒有提供 trigger 方案來主動喚醒監測變化。

#總結

本質上,shallowRef 是特殊的 shallowReactive,而 ref 是特殊的 reactive。明白了這一點,理解兩者的異同就會簡單許多。

 

    // ref -> reactive
    // ref(10) ->  reactive({value:10})
    // shallowRef ->  shallowReactive
    // shallowRef(10)  ->  shallowReactive({value: 10})
    // 所以如果是通過shallowRef創建的數據, 它監聽的是.value的變化 // 因為底層本質上value才是第一層

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM