探討Vue的watch機制

前言

在Vue.js我們常常會使用watch來做一些對應處理,例如可以監控某值出現變化的時候,就觸發function去做事情,有時候我們也會需要拿到變化前的值或變化後的值去做利用。此篇我們來探討一下watch機制如何運作。

監控ref定義的響應式數據

請特別注意,這邊提到的是數據,不是物件

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
import { ref, watch } from 'vue'
const sum = ref(0)
watch(sum,(newVal,oldVal) => {
console.log("newVal",newVal)
console.log("oldVal",oldVal)
})
</script>

<template>
{{sum}}
<button @click="sum++">click</button>
</template>

點擊一下,查看主控台(F12>>console)會印出1和0的值

再點一下newVal和oldVal則再往上累加。
代表監控ref定義的響應式數據

  1. 可以直接針對ref進行監控
  2. 可以成功印出新值和舊值

監控ref定義的多個響應式數據

用陣列將要監控的ref裝起來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import { ref, watch } from 'vue'
const sum = ref(0)
const message= ref('你好啊')
watch([sum,message],(newVal,oldVal) => {
console.log("newVal",newVal)
console.log("oldVal",oldVal)
})
</script>

<template>
{{sum}}
{{message}}
<button @click="sum++">sum click</button>
<button @click="message += '~'">message click</button>
</template>

點選sum click按鈕會印出以下結果

監控reactive所定義的一個響應式物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { reactive, watch } from 'vue'
const person = reactive({
name: 'fred',
age: 18
})

watch(person, (newVal, oldVal) => {
console.log("newVal", newVal)
console.log("oldVal", oldVal)
})
</script>

<template>
{{ person.age }}
<button @click="person.age++">click</button>
</template>

點選click按鈕會印出以下結果

我們會發現newVal的age值變成19了,oldVal竟然也是相同值(因指向同一個對象,共用一份記憶體),代表我們無法取得oldVal的值,這一點要特別留意,不過我們一般的需求也還真的鮮少拿舊的值去做運用,所以影響並沒有太大。

註:reactive也能改為ref,但監控對象就要改寫為person.value,才能偵測到裡面值的變化

監控reactive所定義的一個響應式數值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { reactive, watch } from 'vue'
const person = reactive({
name: 'fred',
age: 18
})

watch(person.age, (newVal, oldVal) => {
console.log("newVal", newVal)
console.log("oldVal", oldVal)
})
</script>

<template>
{{ person.age }}
<button @click="person.age++">click</button>
</template>

瀏覽器會直接報出警告A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
意思是說,watch能監控的對象只能是 getter函式、ref對象、reactive物件對象、陣列,所以上述例子person.age是單純的值是無法監控的,所以要做監控的話我們可以包裝成getter函式,改寫成以下

1
2
3
4
watch(()=>person.age,(newVal,oldVal)=>{
console.log("newVal", newVal)
console.log("oldVal", oldVal)
})

按下click後印出以下結果

這樣就能順利監控到person.age的值了

註:reactive也能改為ref,但監控對象就要改成()=>person.value.age,才能偵測到裡面值的變化

為什麼有時候ref對象要加上.value,有時候不用,判斷標準是什麼

依據對象型態而定

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
<script setup>
import { ref, watch } from 'vue'
const sum = ref(0)
const person = ref({
name: 'fred',
age: 18,
job: {
m1: {
salary: 20
}
}
})
watch(sum, (newVal, oldVal) => {
console.log("newVal", newVal)
console.log("oldVal", oldVal)
})
watch(person.value, (newVal, oldVal) => {
console.log("newVal", newVal)
console.log("oldVal", oldVal)
})
</script>

<template>
{{ sum }}
<button @click="sum++">click</button>
<br>
{{ person.job.m1.salary }}
<button @click="person.job.m1.salary++">click</button>
</template>

像sum的話是一個基本類型數字型態用ref包裝起來的,就不用額外加上.value,因為加上.value就是指裡面0的值,是無法作監控的。
如果是person因為他是一個物件用ref包裝,裡面的屬性值變化時,需要偵測我們就必須監控person.value的值,就是針對Proxy物件來監控,就能偵測到變化。
另外一提,假若現在真的不想要.value,則可將watch改寫為以下,加上{deep:true}做深層監控,也是能監控到最新的值,如何使用還是看個人習慣。

1
2
3
4
watch(person, (newVal, oldVal) => {
console.log("newVal", newVal)
console.log("oldVal", oldVal)
}, { deep: true })

上述範例重點整理

  1. 如果是基本型態的資料要監控,可用getter函式返回
  2. 監控reactive和ref概念一樣,只是要注意ref對象要記得加上.value,加上.value就是等同於reactive對象,就是Proxy物件
  3. 承上,如果ref不要用.value處理,則要用{ deep: true }做深層監控,就能監控到
  4. reactive對象,強制會用深層監控,不用加{ deep: true }

參考資料:官網