
前言
如果你是一名 Vue 開發者,相信你一定格外熟悉 ref 這個響應式 API,但是你知道嗎?其實除了 ref 之外,你還可以用另一種 API 來宣告響應式變數,那就是 shallowRef,而 shallowRef 與 ref 有什麼不同呢?就讓我們來認識一下吧!
ref vs shallowRef
首先我們先來聊一下 ref 再來介紹 shallowRef,ref 是 Vue 3 Composition API 中的一個響應式 API,它可以用來宣告一個響應式變數,當這個變數的值發生變化時,Vue 會自動重新渲染相關的元件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <script setup> import { ref } from 'vue';
const count = ref(0); const increment = () => { count.value += 1; } </script>
<template> <div> <p>count:{{ count }}</p> <button @click="increment">+1</button> </div> </template>
<style> </style>
|
我們透過上面的範例來講,可以看到當我們點擊按鈕時,count 的值會發生變化,並且畫面也會重新渲染。
而我們實際開發上也很常使用 ref 來宣告響應式的深層物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script setup> import { ref } from 'vue';
const user = ref({ name: 'Ray', url: 'https://israynotarray.com', address: { city: 'Kaohsiung', country: 'Taiwan' } });
const updateAddress = () => { user.value.address.city = 'Taipei'; } </script>
<template> <div> <p>city:{{ user.address.city }}</p> <button @click="updateAddress">Update Address</button> </div> </template>
|
那麼為什麼要特別提到 shallowRef 呢?因為 ref 在監聽時,所採用的方式是「深層遞迴」,也就是說物件不管有多深,只要有變化,Vue 都會重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <script setup> import { ref } from 'vue';
const deepObj = ref({ level1: { level2: { level3: { level4: { level5: { level6: { level7: { level8: { level9: 'Hello Ray!', } } } } } } } } });
const updateDeepObj = () => { deepObj.value.level1.level2.level3.level4.level5.level6.level7.level8.level9 = 'Hello Vue 3!'; } </script>
<template> <div> <p>deepObj:{{ deepObj.level1.level2.level3.level4.level5.level6.level7.level8.level9 }}</p> <button @click="updateDeepObj">Update deepObj</button> </div> </template>
|
我們可以透過上方範例看到,不管我們改變多深的層級,只要有變化,Vue 都會重新渲染,其實這樣的行為在某些情況下是不必要的,甚至在某些狀況下根本沒有必要這麼深層的去監聽,像是…取得 DOM 元素
1 2 3 4 5
| <script setup> import { ref } from 'vue';
const dom = ref(document.querySelector('h1')); </script>
|
很多時候我們只是需要取得 DOM 元素,並不需要把取出來的 DOM 元素完整的進行監聽,而這種深層的監聽行為會導致效能的浪費。
接下來就要輪到我們這一篇要介紹的重頭戲了,也就是 shallowRef,shallowRef 與 ref 一樣都是 Vue 3 Composition API 中的響應式 API,不過 shallowRef 有一個特性,就是只會監聽物件的第一層屬性,當第一層屬性發生變化時,Vue 才會重新渲染。
什麼意思呢?我們拿剛剛的超級巢狀(deepObj)物件來舉例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <script setup> import { shallowRef } from 'vue';
const deepObj = shallowRef({ level1: { level2: { level3: { level4: { level5: { level6: { level7: { level8: { level9: 'Hello Ray!', } } } } } } } } });
const updateDeepObj = () => { deepObj.value.level1.level2.level3.level4.level5.level6.level7.level8.level9 = 'Hello Vue 3!'; } </script>
<template> <div> <p>deepObj:{{ deepObj.level1.level2.level3.level4.level5.level6.level7.level8.level9 }}</p> <button @click="updateDeepObj">Update deepObj</button> </div> </template>
|
透過上面的範例,我們可以發現這次點擊按鈕時,畫面並沒有重新渲染,這就是 shallowRef 的特性,也就是只會監聽物件的第一層屬性,當第一層屬性發生變化時,Vue 才會重新渲染。
但是這個第一層又有一點特別了,所以我們要來細細的講解一下。
shallowRef 的特性
前面有提到 shallowRef 只會監聽第一層,所以你可能會興高采烈的馬上去使用 shallowRef,然後你就會發現…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script setup> import { shallowRef } from 'vue';
const user = shallowRef({ name: 'Ray', url: 'https://israynotarray.com', address: { city: 'Kaohsiung', country: 'Taiwan' } });
const updateAddress = () => { user.value.name = 'Vue 3'; } </script>
<template> <div> <p>name:{{ user.name }}</p> <button @click="updateAddress">Update Name</button> </div> </template>
|
這下有趣了,你不管怎麼點擊按鈕,畫面都不會重新渲染,你可能會認為這是 shallowRef 有 Bug!但其實不對,原因是因為,真正的第一層是 user.value,所以當你要改變 user 的值時,你應該要這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <script setup> import { shallowRef } from 'vue';
const user = shallowRef({ name: 'Ray', url: 'https://israynotarray.com', address: { city: 'Kaohsiung', country: 'Taiwan' } });
const updateAddress = () => { user.value = { name: 'Vue 3', url: 'https://vuejs.org', address: { city: 'Taipei', country: 'Taiwan' } }; } </script>
<template> <div> <p>name:{{ user.name }}</p> <button @click="updateAddress">Update Name</button> </div> </template>
|
這樣當你點擊按鈕時,畫面就會重新渲染了。
透過上面的實驗特性,我們可以這樣來驗證:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <script setup> import { shallowRef } from 'vue';
const user = shallowRef({ name: 'Ray', url: 'https://israynotarray.com', address: { city: 'Kaohsiung', country: 'Taiwan' } });
const updateAddress = () => { user.value.name = 'Vue 3'; }
const updateAddress2 = () => { user.value = { name: 'Vue 3', url: 'https://vuejs.org', address: { city: 'Taipei', country: 'Taiwan' } }; }
const arr = shallowRef([1, 2, 3]);
const updateArr = () => { arr.value[0] = 4; }
const updateArr2 = () => { arr.value = [4, 5, 6]; } </script>
<template> <div> <p>name:{{ user.name }}</p> <button @click="updateAddress">Update Name</button> <button @click="updateAddress2">Update Name 2</button> <p>arr:{{ arr }}</p> <button @click="updateArr">Update Arr</button> <button @click="updateArr2">Update Arr 2</button> </div> </template>
|
相信解釋到這邊,你應該對於 shallowRef 有了更深一層的了解,也知道了 shallowRef 與 ref 之間的差異。
Note
除了 shallowRef 之外,還有 shallowReactive。
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement