watch 是 VUE 派來偷聽的 SPY

初學 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>

checkbox 全選 watch 範例實作

Leave a Reply

Your email address will not be published. Required fields are marked *