今天要來學習有關 watchers 的觀念,我會將它理解為 onchange
的概念,但傳統的 onchange
指支援監聽 DOM 元素變化,而 watch 可以用在任何響應式狀態上
這邊使用 watch
監聽如果文字有包含 ?
就會執行 getAnswer()
函式,並使 answer 變為 yes / no
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)
const attr = 'box'
// 監聽 question 的變化
watch(question, (newQuestion) => {
if (newQuestion.includes('?')) {
getAnswer()
}
})
// 非同步請求 API
async function getAnswer() {
loading.value = true
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
const data = await res.json()
answer.value = data.answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
} finally {
loading.value = false
}
}
</script>
<template>
<div :class="attr">
<p>
Ask a yes/no question:
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</div>
</template>
<style scoped>
.box{
gap: 5px;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
font-size: x-large;
border: solid;
margin-left: 650px;
margin-top: 5px;
height: 200px;
width:350px;
padding: 35px;
}
</style>
watch
預設情況屬於淺層監聽,當加上 deep: true
則為深層監聽,watch
就能 catch 到內部的變化
<script setup>
import { ref, watch } from 'vue'
const user = ref({
name: 'Tom',
address: { city: 'Taipei' }
})
watch(user, (newVal, oldVal) => {
console.log('user 變了!', newVal)
}, { deep: true })
function changeCity(newCity) {
user.value.address.city = newCity
}
</script>
<template>
<div class="box">
<p>使用者:{{ user.name }}</p>
<p>居住城市:{{ user.address.city }}</p>
<input
type="text"
v-model="user.address.city"
placeholder="輸入新城市"
/>
<button @click="changeCity('Taichung')">改成 Taichung</button>
<button @click="changeCity('Kaohsiung')">改成 Kaohsiung</button>
</div>
</template>
<style scoped>
.box{
gap: 5px;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
font-size: x-large;
border: solid;
margin-left: 650px;
margin-top: 5px;
height: 300px;
width:350px;
padding: 35px;
}
</style>
當我們把 deep: true
拿掉的話,則不會印出 -> user 變了!
因深層監聽其實是 Vue 使用遞迴走訪整個物件樹,所以較大的物件或陣列則可能會有效能不佳的狀況,若我們只想要監聽某個深層屬性,則直接指定該屬性即可(註:用 .
隔開,但不支持表達式)
watch(() => user.value.address.city, (newCity, oldCity) => {
console.log('城市改了:', newCity)
})
另一個狀況是 watch
預設是 lazy 的,也就是變化時會觸發,immediate: true
可以使建立監聽器的當下,立即 call 一次內部的程式碼
eg. 假設我們想要一開始就將將主題設為黑色,並且之後隨著 user 的喜好調整這時就可以使用
const theme = ref('dark')
watch(theme, (val) => {
document.body.className = val
}, { immediate: true })
ref:
https://zh-hk.vuejs.org/guide/essentials/watchers.html#eager-watchers
https://ithelp.ithome.com.tw/m/articles/10204091