侦听器

来源: 2024-05-25 10:15:40 播报

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) {
    // 数据加载后执行某些操作...
  }
})