初學 VUE 的時候常常混淆 computed 與 watch 的功能。用了幾次之後,慢慢產生一些基礎的認識,今天就讓我來分享一下 watch 要如何使用及要注意的事情:
- computed 的作用是在於根據資料變化去產生新的資料,在載入時就會觸發。簡言之,就是用資料產資料。
- watch 則是觀察資料的變化去執行一些事情,例如打 API、執行函式、產生畫面互動之類。載入時不會觸發,要等待觀察的資料發生變化時才會觸發 watch。換句話說,就是監聽資料的變化去產生動作。
watch 的基礎語法:
watch(X, (newX, oldX) => {
console.log(`x is ${newX}`)
})第一個參數 X 是要觀察的資料, X 可以是一個 ref 、一個 computed 屬性、 一個 reactive 物件、一個 getter 函數,甚至是多個資料類型的陣列。
官網上的列舉說明:
const x = ref(0)
const y = ref(0)
// 單個 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函數
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多個資料類型的陣列
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})觀察以下這段 code 的 console,我們可以認知到:
- watch 陣列,我們可以觀察到 a、b 輸入框所輸入的數值的新舊值。
- watch computed,可以觀察到 c 的計算結果。
- watch 函式,可以觀察到 a+b 函式所返回的結果。
<script setup>
import { ref, computed, watch } from 'vue'
const a = ref(1);
const b = ref(2);
const c = computed(() => {
return a.value + b.value
})
// watch 陣列 newValue 是輸入框的新值組成的陣列
watch([a, b], (newValue,oldValue) => {
console.log('陣列',newValue,oldValue)
})
// watch computed 的 newValue 是 a+b 的結果
watch(c, (newValue,oldValue) => {
console.log('computed',newValue,oldValue)
})
// watch 函式 的 newValue 是 a+b 的結果
watch(()=>a.value + b.value, (newValue,oldValue) => {
console.log('函式',newValue,oldValue)
})
</script>
<template>
<input v-model.number="a"> + <input v-model.number="b"> = {{ c }}
</template>
深層監聽與 reacive 物件
但是要注意的是:不能直接監聽 reactive 物件的屬性值(property),而是要返回那個屬性的 getter 函數。
因為真正會響應的是 reactive 物件,而不是 reactive 物件的屬性值。
const obj = reactive({ count: 0 })
// this won't work because we are passing a number to watch()
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})返回那個 reactive物件 屬性的 getter 函數才是正解:
// instead, use a getter:
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)或是監聽整個 reactive 物件,這樣的 watch 方式會默默地觀察物件的每一個屬性,缺點是物件的每個屬性產生變化時都會觸發 watch 。 這樣會影響效能的開銷。
watch reactive 物件,當誤剪的屬性改變時觸發 watch, 這時 newValue 跟 oldValue 會一樣,因為它們都指向同一個物件,這種情況如果要對 newValue 與 oldValue 做處理,會是一個議題。
<script setup>
import { reactive, watch } from 'vue'
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
console.log('reactive',newValue, oldValue)
})
</script>
<template>
<input v-model.number="obj.count">
</template>
{ deep: true }
當 watch 的是一個函式返回的 reactive 物件內嵌套物件時,物件內的屬性改變並不會觸發 watch。
<script setup>
import { reactive,watch } from 'vue'
const msg = reactive({a:1,b:'hello',c:{c1:2}})
watch(()=>msg.c,(oldValue,newValue)=>{
console.log(oldValue,newValue)
})
// 不會觸發 watch
</script>
<template>
<h1>{{ msg }}</h1>
msg.a : <input v-model="msg.a"><br>
msg.c.c1 : <input v-model="msg.c.c1">
</template>加上 { deep: true } 才會觸發 watch,但是 { deep: true } 會遍歷 reactive 物件內所有的屬性,對於效能消耗較大,須謹慎使用。
<script setup>
import { reactive,watch } from 'vue'
const msg = reactive({a:1,b:'hello',c:{c1:2}})
watch(()=>msg.c,(oldValue,newValue)=>{
console.log(oldValue,newValue)
},{ deep: true })
</script>
<template>
<h1>{{ msg }}</h1>
msg.a : <input v-model="msg.a"><br>
msg.c.c1 : <input v-model="msg.c.c1">
</template>
在網頁載入時觸發 watch
{ immediate: true }
watch 預設為觀察的資料來源發生變化,才會觸發。所以當網頁載入時初始化,並不會觸發 watch,所以如果想在初始化的時候就觸發 watch 必須加上 immediate: true 強制觸發 watch 在初始化的時候執行。
watch(source, (newValue, oldValue) => {
// 立即執行,且當 source 改變時再次執行
}, { immediate: true })加上{ immediate: true }之後,初始化就會執行 watch ,這時 oldValue 是 undefined。
<script setup>
import { ref,watch } from 'vue'
const msg = ref('Hello World!')
watch(msg,(oldValue,newValue)=>{
console.log(oldValue,newValue)
},{immediate:true})
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg">
</template>
