侦听器
基本示例
watch 的作用是用于监测响应式属性的变化,当响应式属性发生改变时执行特定的操作。
基础演示:
vue
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)
// 可以直接侦听一个 ref 的变动
watch(question, async (newQuestion, oldQuestion) => { // 使用一个异步匿名箭头函数(也叫做回调函数)作为参数传递进去
if (newQuestion.includes('?')) {
loading.value = true
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
} finally {
loading.value = false
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</template>参数解释:
watch的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。第二个参数是一个回调函数,函数的两个参数分别是该数据源改变前和改变后的值。
深层监听
如果你要监听的是一个对象或数组,并希望对其内部的嵌套属性进行监听,可以使用 deep: true 选项。这会监听对象的所有属性变化。deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。
vue
<script>
const state = reactive({
user: {
name: 'Alice',
age: 25
}
});
watch(obj, (newValue, oldValue) => { // 监听整个对象
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
console.log('User object changed:', newVal, oldVal);
},
{ deep: true } // 启用深度监听
);
</script>即时回调
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。
但可以通过传入 immediate: true 选项,在创建侦听器时,立即执行一遍回调。
vue
<script>
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
)
</script>一次性监听器
使用 once: true 选项,监听器只会触发一次。
vue
<script>
watch(
source,
(newValue, oldValue) => {
// 当 `source` 变化时,仅触发一次
},
{ once: true }
)
</script>自动追踪
一个新的函数,主要用于偷懒,省略了数据源参数以及immediate: true选项。
vue
<script>
watchEffect(async () => { // 会自动追踪内部用到的响应式数据,无需数据源参数
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
// 会自动追踪 todoId.value,当该值被修改时,匿名函数(闭包)会重新执行
)
data.value = await response.json()
}) // 在创建时会强制执行一次,无需 immediate: true 选项
</script>等于
vue
<script>
const todoId = ref(1)
const data = ref(null)
watch(
todoId, // 第一次使用 todoId
async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}` // 第二次使用 todoId
)
data.value = await response.json()
},
{ immediate: true }
)
</script>阻止覆盖过时的数据
监听器内部的匿名函数采用异步的方式执行,容易出现问题,若某次执行时间被延长导致数据无法被即时赋值,但更新的数据已经被赋值了,这时候要阻止之前执行的匿名函数,防止拿到过时的数据。
使用 onWatcherCleanup() API 来注册一个清理函数,当侦听器失效并准备重新运行时会被调用:
vue
<script>
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// 回调逻辑
})
onWatcherCleanup(() => {
// 终止过期请求
controller.abort()
})
})
</script>