watch
侦听明确指定的状态变化执行回调
实战场景
- 侦听路由传参的变化,重新访问接口,刷新页面
- 侦听接口返回值的变化,刷新页面
侦听值类型数据
// 选项式 API watch: { // 每当 question 改变时,这个函数就会执行 question(newQuestion, oldQuestion) { // 第一个参数是新值,第二个参数是旧值 } },
// 组合式 API <script setup> import { ref, watch } from 'vue' let question = ref('') watch( question, (newQuestion, oldQuestion) => { // 第一个参数是新值,第二个参数是旧值 } ) </script>
侦听值类型的对象属性
// 选项式 API watch: { // 监听对象person中gender属性的变化 'person.gender':function(new_gender){ }, },
// 组合式 API watch( // 不能直接写 person.gender,会导致watch函数的第一个参数会得到一个字符串,不具有响应式,应改用返回对象属性的 getter 函数 () => person.gender, (new_gender) => { } )
侦听引用类型数据
选项式API需添加深度侦听 deep
watch选项默认是浅层监听,无法监听到嵌套属性的变化,所以监听对象时通常需要使用深度监听
// 选项式 API watch: { person: { // 在嵌套的属性变更时触发 handler(newValue, oldValue) { // 因对象是引用类型的数据,对象属性发生变化时,对象引用的地址并没有发生变化,所以只要没有替换对象本身,这里的 `newValue` 和 `oldValue` 相同 }, // 开启深度监听 deep: true } }
注意事项
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
组合式API默认就是深度侦听
// 组合式 API let person = reactive({ age: 35 }) watch( person, // 在嵌套的属性变更时触发 (newValue, oldValue) => { // 因对象是引用类型的数据,对象属性发生变化时,对象引用的地址并没有发生变化,所以只要没有替换对象本身,这里的 `newValue` 和 `oldValue` 相同 } )
返回响应式对象的 getter 函数,只在返回不同的对象时触发回调:
watch( () => state.someObject, () => { // 仅当 state.someObject 被替换时触发 } )
通过 deep 选项,可强制转成深层侦听器
watch( () => state.someObject, (newValue, oldValue) => { // 此处`newValue` 和 `oldValue` 是相等的,除非 state.someObject 被整个替换 }, { deep: true } )
注意事项
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
侦听多个数据
// 选项式 API watch: { x(newX,oldX) { }, 'y.value':function(newY) { }, },
// 选项式 API watch([x, () => y.value], ([newX, newY]) => { })
watch 函数的第一个参数可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。
立即侦听 immediate
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。
但有时需要在侦听器被创建时就触发监听
// 选项式 API watch: { question: { handler(newValue, oldValue) { // 在组件实例创建时会立即执行(在 created 钩子之前执行,此时 data、computed 和 methods 选项都是可用的) }, // 开启立即监听 immediate: true } }
// 组合式 API watch( question, (newValue, oldValue) => { // 在组件实例创建时会立即执行(在 created 钩子之前执行,此时 data、computed 和 methods 选项都是可用的) }, // 开启立即监听 { immediate: true } )
单次侦听 once
vue3.4以上版本的新增功能:侦听器只执行一次
// 选项式 API watch: { source: { handler(newValue, oldValue) { // 当 `source` 变化时,仅触发一次 }, // 开启单次监听 once: true } }
// 组合式 API watch( source, (newValue, oldValue) => { // 当 `source` 变化时,仅触发一次 }, // 开启单次监听 { once: true } )
改变侦听回调的触发时机
vue更新前回调(同步侦听)
若想在 Vue 进行任何更新之前触发侦听回调
,则需添加 flush: 'sync'
选项,将其变成一个同步侦听器
// 选项式 API watch: { key: { handler() {}, // 提前侦听回调 flush: 'sync' } }
// 组合式 API watch(source, callback, { flush: 'sync' })
注意事项
同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。
【默认】vue更新后,DOM 更新前回调
watch 的回调默认在父组件更新 (如有) 之后、所属组件的 DOM 更新之前
执行,所以此时回调中访问的是所属组件更新之前的 DOM
DOM 更新后回调(后置侦听)
要想在回调中访问的是所属组件更新之后的 DOM
,需添加 flush: 'post'
选项
// 选项式 API watch: { key: { handler() {}, // 延后侦听回调 flush: 'post' } }
// 组合式 API watch(source, callback, { flush: 'post' })
watchEffect()
仅组合式 API 可用
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
const count = ref(0) watchEffect(() => { // 在创建时,即执行第一次回调,并开始侦听count,在后续每次 count 发生变化时,都会触发回调 console.log(count.value) })
- 自动追踪所有能访问到的响应式属性执行回调
- 在创建时,就会执行一次(与 watch 添加了 immediate 选项的效果相同,其目的是收集要侦听的数据)
适用场景
- 侦听的目标依赖于多个响应式数据
- 侦听嵌套数据结构中的多个属性(可能会比深度侦听器性能更佳,因为它只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性)
注意事项
watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。
同步侦听
vue更新前回调
import { watchEffect} from 'vue' watchEffect(callback, { flush: 'sync' })
或
import { watchSyncEffect } from 'vue' watchSyncEffect(() => { /* 在响应式数据变化时同步执行 */ })
watchSyncEffect 是同步触发的 watchEffect 的别名
注意事项
同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。
后置侦听
DOM 更新后回调
import { watchEffect} from 'vue' watchEffect(callback, { flush: 'post' })
或
import { watchPostEffect } from 'vue' watchPostEffect (() => { /* 在 Vue 更新后执行 */ })
watchPostEffect 是后置刷新的 watchEffect 的别名
官方范例
watchEffect(async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}` ) data.value = await response.json() })
与下方 watch 的写法相比, watchEffect 更加方便和简洁
const todoId = ref(1) const data = ref(null) watch( todoId, async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}` ) data.value = await response.json() }, { immediate: true } )
watch 和 watchEffect() 的区别
- watch 只追踪明确侦听的数据源,在回调执行期间会避免追踪依赖,能更加精确地控制回调函数的触发时机。
- watchEffect 会自动追踪所有能访问到的响应式属性,在回调执行期间会追踪依赖,导致其响应性依赖关系有时会不那么明确。
用 $watch() 创建侦听器
this.$watch('question', (newQuestion) => { })
适用场景:
- 在特定条件下设置一个侦听器
- 只侦听响应用户交互的内容
- 需要提前停止侦听器
手动停止侦听器
- 用 watch 选项或者 $watch() 实例方法声明的侦听器,会在宿主组件卸载时自动停止。
- 在 setup() 或
<script setup>
中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。
- 用异步回调创建的侦听器,不会绑定到当前组件上,必须手动停止它,以防内存泄漏。
<script setup> import { watchEffect } from 'vue' // 异步回调创建的侦听器必须手动停止! setTimeout(() => { watchEffect(() => {}) }, 100) </script>
注意事项
需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,可以用条件式的侦听逻辑。
// 需要异步请求得到的数据 const data = ref(null) watchEffect(() => { if (data.value) { // 数据加载后执行某些操作... } })
- 调用 $watch() 或 watchEffect() 返回的函数,可手动停止侦听器
const unwatch = this.$watch('question', (newQuestion) => { }) // 当该侦听器不再需要时,停止该侦听器 unwatch()
const unwatch = watchEffect(() => {}) // ...当该侦听器不再需要时 unwatch()