watch 函数在每次响应式状态发生变化时触发回调函数。
示例:
<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 函数、或多个数据源组成的数组
watch 的第二个参数是回调函数。
watch 的第三个参数是参数配置,例如深度监听,一次监听和立即监听。
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}`)
})
注意:我们不能直接侦听响应式对象的属性值。这里需要用一个返回该属性的 getter 函数。
示例:
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`Count is: ${count}`)
})
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`Count is: ${count}`)
}
)
深层侦听器
侦听响应式对象,回调函数在所有嵌套的属性变更时都会被触发。
示例:
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})
obj.count
侦听返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调函数。
示例:
watch(
() => state.someObject,
() => {
// 仅当 state.someObject 被替换时触发
}
)
将上面示例加上 deep 选项,强制转成深层侦听器。
示例:```javaScript
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:newValue
此处和 oldValue
是相等的
// 除非 state.someObject 被整个替换了
},
{ deep: true }
)
### 即时回调的侦听器
创建侦听器时,立即执行一遍回调,需设置immediate: true 选项。
示例:
```javaScript
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
)
一次性侦听器
需设置once: true 选项。
示例:
watch(
source,
(newValue, oldValue) => {
// 当 `source` 变化时,仅触发一次
},
{ once: true }
)
watchEffect()
对多个依赖项的侦听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。
示例:
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
watch vs. watchEffect
watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
回调的触发时机
Post Watchers?
如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: 'post' 。
示例:
watch(source, callback, { flush: 'post' })
watchEffect(callback, { flush: 'post' })
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
同步侦听器
创建一个同步触发的侦听器,它会在 Vue 进行任何更新之前触发。
示例:
watch(source, callback, {
flush: 'sync'
})
watchEffect(callback, {
flush: 'sync'
})
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {
/* 在响应式数据变化时同步执行 */
})
停止侦听器
同步语句创建的侦听器,会自动绑定到宿主组件实例上(侦听器必须用同步语句创建);如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
示例:
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数。
示例:
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
需要等待一些异步数据,使用条件式的侦听逻辑。
示例:
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})