[筆記] Vue3 watch 用法 — 4 種數據、5 種情境

Ivy Ho
IvyCodeFive
Published in
11 min readJan 24, 2024
Photo by Paul Hanaoka on Unsplash

Vue3 watch 可監視的數據分為以下 4 種 :

  1. ref 定義的數據(包括計算屬性)
  2. reactive 定義的數據
  3. getter 函數(函數返回一個值)
  4. 一個包含上述內容的陣列

(參考 官方文檔內容)

分別用以下 5 種情境進行說明:

  • 情況一、監視 ref定義的基本類型數據
  • 情況二、監視 ref 定義的物件類型數據
  • 情況三、監視 reactive定義的物件類型數據 (reactive 無法定義基本類型數據)
  • 情況四、監視 refreactive定義的物件類型數據中的某個屬性
  • 情况五、監視上述的多個數據

情況一、監視 ref 定義的基本類型數據

用法 : watch(要監視的對象, 回呼函式)

  • 要監視的對象直接寫數據名即可 (不需要加.value )
const sum = ref(0)

watch(sum,(newValue, oldValue) => {
console.log('sum 改變了', newValue, oldValue)
})
  • 可用以下方式解除 watch
const sum = ref(0)

const stopWatch = watch(sum, (newValue, oldValue)=>{
console.log('sum 改變了', newValue, oldValue)

if(newValue >= 10) {
stopWatch()
}
})

情況二、監視 ref 定義的物件類型數據

1 . 監聽對象直接寫數據名

const person = ref({
name: 'Taylor',
age: 20
})

watch(person, (newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
})
  • 監視的是物件的地址值,當 person 物件的地址值改變,才會觸發。
  • 當物件地址值改變,能夠成功取得不同的 newValue 和 oldValue。
  • 以下範例,只有按下"換掉整個人" ,watch 才會監測到 :
<template>
<div>
<p>姓名 : {{ person.name }}</p>
<p>年齡 : {{ person.age }}</p>

<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年齡</button>
<button @click="changePerson">換掉整個人</button>
</div>
</template>

<script setup>
import { ref, watch } from 'vue'

const person = ref({
name: 'Taylor',
age: 20
})

const changeName = () => {
person.value.name += '~'
}

const changeAge = () => {
person.value.age += 10
}

const changePerson = () => {
person.value = {
name: 'Ari',
age: 18
}
}

watch(person, (newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
})
</script>

2 . 如果希望當物件內任一屬性改變,都會觸發 watch 回呼函式,需手動開啟深度監視 { deep:true }

watch(
person,
(newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
},
{ deep: true }
)
  • 加上 { deep: true } 之後,無論換掉物件內的屬性,還是一整個物件重新賦值,watch 都能監測到,並且觸發回呼函式。

需要注意的問題

此時按下 “修改姓名” 和 “修改年齡” 雖然能夠觸發 watch,但 newValue 與 oldValue 抓到的物件內容是相同的,皆會顯示修改後的數據。

補充: { immediate: true }

  • 除了{ deep: true } ,還可設定其他配置項目,常見的還有將 immediate 設為 true,在頁面初始就會先執行一次 watch 的回呼函式。
watch(person, (newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
},{ deep: true, immediate: true })

情況三、監視 reactive 定義的物件類型數據

  • 直接監聽數據名,默認開啟深度監視,不須加上{ deep: true }
  • 即使加上 { deep: false} 也無法關閉深度監視。
const person = reactive({
name: 'Taylor',
age: 20
})

watch(person, (newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
})

由於使用 reactive 所定義的數據,無法對其物件重新賦值,只能修改物件內的屬性,所以我們將 changePerson 改為使用 Object.assign 的方式去換掉整個人,範例修改如下 :

<template>
<div>
<p>姓名 : {{ person.name }}</p>
<p>年齡 : {{ person.age }}</p>

<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年齡</button>
<button @click="changePerson">換掉整個人</button>
</div>
</template>

<script setup>
import { watch, reactive } from 'vue'

const person = reactive({
name: 'Taylor',
age: 20
})

const changeName = () => {
person.name += '~'
}

const changeAge = () => {
person.age += 10
}

const changePerson = () => {
Object.assign(person, {
name: 'Ari',
age: 18
})
}

watch(person, (newValue, oldValue) => {
console.log('person 改變了', newValue, oldValue)
})
</script>

需要注意的問題

此時按下 “修改姓名” 和 “修改年齡” 甚至 “修改整個人”,oldValue 與 newValue 抓到的仍然是相同內容的物件,皆顯示修改後的數據。

為什麼 ref 所定義的物件,在按下 “修改整個人” 可以抓到不同的新舊值,改為用 reactive 定義物件卻不行?

使用 ref 定義物件類型數據,我們可以使用 person.value 來對其重新賦值。剛剛提到,由於 reactive 的使用方法是預設無法對其定義的物件重新賦值的,只能修改物件內的屬性,在 changePerson 我們使用 Object.assign來替換掉物件內的屬性,實質上地址值是沒有改變的。

情況四、監視 ref 或 reactive 定義的物件類型數據中的某個屬性

  1. 監視(由 ref 或 reactive 定義的)響應式物件中的某個屬性,且該屬性為基本類型
  • 當我們只想在 person 物件內的 name 屬性產生變化時,才觸發 watch 回呼函式
  • 寫法需要將監聽的對象寫成一個 getter 函數 (一個函數返回一個值)
  • 監聽的對象也就變成 ()=> { return person.value.name } (以 ref 為例)
  • 可簡寫為 ()=> person.value.name
watch(
() => person.value.name,
(newValue, oldValue) => {
console.log('person 內的 name 改變了', newValue, oldValue)
}
)
  • 此時監視的對象很具體指明為 person.value.name,在這種方法下, newValue 和 oldValue 也能成功抓到不同的新舊值。

2. 監視(由 ref 或 reactive 定義的)響應式物件中的某個屬性,且該屬性為物件類型

將範例中 person 數據內新增第二層 albums 物件 (以 ref 為例):

<template>
<div>
<p>姓名 : {{ person.name }}</p>
<p>年齡 : {{ person.age }}</p>
<p>作品 : {{ person.albums.album1 }} , {{ person.albums.album2 }}</p>

<button @click="changeAlbum1">換掉專輯一</button>
<button @click="changeAlbum2">換掉專輯二</button>
<button @click="changeAlbums">換掉全部專輯</button>
</div>
</template>

<script setup>
import { ref, watch } from 'vue'

const person = ref({
name: 'Taylor',
age: 20,
albums: {
album1: 'folklore',
album2: 'evermore'
}
})

const changeAlbum1 = () => {
person.value.albums.album1 = 'Positions'
}

const changeAlbum2 = () => {
person.value.albums.album2 = 'Eternal Sunshine'
}
const changeAlbums = () => {
person.value.albums = { album1: 'Heard It in a Past Life', album2: 'Surrender' }
}
</script>

當我們想要監視 person 內的 albums 物件

  • 建議要將監聽對象寫成一個 getter 函數 (一個函數返回一個值)
  • 監聽的是 albums 物件的地址值
  • 此時只有按下 "換掉全部專輯",才會觸發 watch
  • 可成功取得不同的新舊值
watch(
() => person.value.albums,
(newValue, oldValue) => {
console.log('person.value.albums 改變了', newValue, oldValue)
}
)

當我們希望在 albums 內屬性變化時,也觸發 watch

  • 需要再手動開啟深度監視 { deep: true }
  • 此時按下 “換掉專輯一”、“換掉專輯二”、“換掉全部專輯” 皆會觸發 watch
  • 但只有按下 “換掉全部專輯” 時能成功取得不同的新舊值,前兩者無法取得不同的新舊值。
watch(
() => person.value.albums,
(newValue, oldValue) => {
console.log('person.value.albums 改變了', newValue, oldValue)
},
{ deep: true }
)

情况五、監視上述的多個數據

  • 用法是將多個數據使用陣列包起來
  • 其實就是延續情況一 ~ 情況四的那些監聽方法,並且需要監聽什麼就放進陣列裡。
  • 例如我們希望監視 person 中的 name 屬性以及 albums 物件,且希望在 albums 內的屬性有變動時也能觸發 watch :
watch(
[() => person.value.name, () => person.value.albums],
(newValue, oldValue) => {
console.log('person albums 改變了', newValue, oldValue)
},
{ deep: true }
)
  • newValue 和 oldValue 抓到的會是一整個陣列 (是否能成功取得不同的新舊值,參考情況一~情況四)
  • 不同數據改變,都會觸發同一個回呼函式,如果不希望如此,將他們分開監視是比較好的做法。

重點回顧

  • 用 ref 定義的基本類型數據,作為監視對象,名稱不須加上 .value
  • 用 reactive 定義的物件類型數據,作為監視對象,預設開啟深度監視,不須額外設定 { deep: true } 來監視(只有一層)物件型別內的任一屬是否性產生變化。

為了方便對照,我將此篇內容整理成以下表格 :

--

--

Ivy Ho
IvyCodeFive

"You don't have to be great to start, but you have to start to be great."