[筆記] Vue3 watch 用法 — 4 種數據、5 種情境
Vue3 watch 可監視的數據分為以下 4 種 :
- ref 定義的數據(包括計算屬性)
- reactive 定義的數據
- getter 函數(函數返回一個值)
- 一個包含上述內容的陣列
(參考 官方文檔內容)
分別用以下 5 種情境進行說明:
- 情況一、監視
ref
定義的基本類型數據 - 情況二、監視
ref
定義的物件類型數據 - 情況三、監視
reactive
定義的物件類型數據 (reactive 無法定義基本類型數據) - 情況四、監視
ref
或reactive
定義的物件類型數據中的某個屬性 - 情况五、監視上述的多個數據
情況一、監視 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 定義的物件類型數據中的某個屬性
- 監視(由 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 }
來監視(只有一層)物件型別內的任一屬是否性產生變化。
為了方便對照,我將此篇內容整理成以下表格 :