不同於 ref 的 shallowRef 宣告

Vue3

前言

如果你是一名 Vue 開發者,相信你一定格外熟悉 ref 這個響應式 API,但是你知道嗎?其實除了 ref 之外,你還可以用另一種 API 來宣告響應式變數,那就是 shallowRef,而 shallowRefref 有什麼不同呢?就讓我們來認識一下吧!

ref vs shallowRef

首先我們先來聊一下 ref 再來介紹 shallowRefref 是 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 元素完整的進行監聽,而這種深層的監聽行為會導致效能的浪費。

接下來就要輪到我們這一篇要介紹的重頭戲了,也就是 shallowRefshallowRefref 一樣都是 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 有了更深一層的了解,也知道了 shallowRefref 之間的差異。

Note
除了 shallowRef 之外,還有 shallowReactive

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ