Vue: 侦听器(Watch)

1. 侦听器概述

侦听器(Watchers)是 Vue 中用于响应数据变化并执行副作用的重要机制。与计算属性不同,侦听器专注于在状态变化时执行操作(如 DOM 更改、异步操作等)。

1.1 基本语法

TypeScript 复制代码
import { watch } from 'vue'

watch(source, callback, options)

2. 侦听数据源类型

数据源类型 示例 说明
单个 ref watch(x, callback) 直接侦听一个 ref
Getter 函数 watch(() => x.value + y.value, callback) 通过函数返回要侦听的值
多个数据源 watch([x, () => y.value], callback) 同时侦听多个数据源
响应式对象 watch(obj, callback) 隐式创建深层侦听器

2.1 注意事项

不能直接侦听响应式对象的属性值:

TypeScript 复制代码
// 错误方式
watch(obj.count, (count) => {
  console.log(`Count is: ${count}`)
})

// 正确方式:使用getter函数
watch(
  () => obj.count,
  (count) => {
    console.log(`Count is: ${count}`)
  }
)

3. 深层侦听器

3.1 自动深层侦听

当直接侦听响应式对象时,会自动创建深层侦听器:

TypeScript 复制代码
const obj = reactive({ count: 0, nested: { value: 1 } })

watch(obj, (newValue, oldValue) => {
  // 任何嵌套属性变化都会触发
  // 注意:newValue和oldValue是同一个对象
})

obj.nested.value++ // 会触发侦听器

3.2 选择性深层侦听

对于getter函数返回的对象,默认只侦听引用变化:

TypeScript 复制代码
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 仅当state.someObject被替换时触发
  }
)

可以使用deep选项强制深层侦听:

TypeScript 复制代码
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 嵌套属性变化也会触发
  },
  { deep: true } // 或 { deep: 3 } (Vue 3.5+,表示最大遍历深度)
)
TypeScript 复制代码
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 嵌套属性变化也会触发
  },
  { deep: true } // 或 { deep: 3 } (Vue 3.5+,表示最大遍历深度)
)

4. watchEffect

watchEffect自动追踪其同步执行期间访问的所有响应式属性:

TypeScript 复制代码
// 使用watch
watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)

// 使用watchEffect简化
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

4.1 watch vs watchEffect 对比

特性 watch watchEffect
依赖追踪 只追踪明确侦听的数据源 自动追踪所有访问到的响应式属性
初始执行 需要immediate: true 立即执行
参数 (source, callback, options) (effect, options)
适用场景 需要精确控制触发时机 依赖多个属性,简化代码

5. 高级选项

5.1 即时回调

TypeScript 复制代码
watch(
  source,
  (newValue, oldValue) => {
    // 立即执行,且当source改变时再次执行
  },
  { immediate: true }
)

5.2 一次性侦听器 (Vue 3.4+)

TypeScript 复制代码
watch(
  source,
  (newValue, oldValue) => {
    // 当source变化时,仅触发一次
  },
  { once: true }
)

5.3 回调触发时机

TypeScript 复制代码
// 默认:父组件更新后,当前组件DOM更新前
watch(source, callback)

// DOM更新后执行
watch(source, callback, { flush: 'post' })
// 或使用别名
watchPostEffect(() => {})

// 同步执行
watch(source, callback, { flush: 'sync' })
// 或使用别名
watchSyncEffect(() => {})

6. 副作用清理

在处理异步操作时,可能需要清理过期的副作用:

6.1 Vue 3.5+ (推荐)

TypeScript 复制代码
watch(id, (newId) => {
  const controller = new AbortController()
  
  fetch(`/api/${newId}`, { signal: controller.signal })
    .then(/* ... */)
  
  onWatcherCleanup(() => {
    controller.abort() // 终止过期请求
  })
})

6.2 兼容版本 (Vue 3.5之前)

TypeScript 复制代码
// 在watch中
watch(id, (newId, oldId, onCleanup) => {
  const controller = new AbortController()
  
  fetch(`/api/${newId}`, { signal: controller.signal })
    .then(/* ... */)
  
  onCleanup(() => {
    controller.abort()
  })
})

// 在watchEffect中
watchEffect((onCleanup) => {
  const controller = new AbortController()
  
  fetch(`/api/${id.value}`, { signal: controller.signal })
    .then(/* ... */)
  
  onCleanup(() => {
    controller.abort()
  })
})

7. 停止侦听器

7.1 自动停止

在组件 setup 中同步创建的侦听器会自动绑定到组件实例,并在组件卸载时自动停止。

7.2 手动停止

异步创建的侦听器需要手动停止:

TypeScript 复制代码
const unwatch = watchEffect(() => {
  // 副作用
})

// 当不再需要时
unwatch()

8. 最佳实践与总结

8.1 选择指南

场景 推荐使用
侦听特定数据源的变化 watch
依赖多个数据源 watchEffect
需要立即执行回调 watchEffectwatch + immediate: true
只需要响应一次变化 watch + once: true
需要访问更新后的DOM watch/watchEffect + flush: 'post'

8.2 性能考虑

  1. 避免不必要的深层侦听,特别是大型数据结构

  2. 使用once: true选项处理只需响应一次的情况

  3. 及时清理不再需要的侦听器,防止内存泄漏

  4. 对于复杂逻辑,考虑使用watch明确指定依赖源

8.3 代码组织建议

TypeScript 复制代码
// 推荐:将相关侦听逻辑组织在一起
const state = reactive({
  userId: 1,
  userData: null,
  loading: false
})

watch(
  () => state.userId,
  async (newId) => {
    state.loading = true
    try {
      const response = await fetch(`/api/users/${newId}`)
      state.userData = await response.json()
    } catch (error) {
      console.error('Failed to fetch user:', error)
    } finally {
      state.loading = false
    }
  },
  { immediate: true } // 立即获取初始数据
)

9. 总结图表

侦听器是Vue响应式系统中处理副作用的核心工具,正确使用它们可以构建出高效、可维护的应用程序。根据具体场景选择合适的侦听方式,并注意性能优化和资源清理,将大大提升代码质量。

相关推荐
秋田君8 小时前
3D热力图封装组件:Vue + Three.js 实现3D图表详解
javascript·vue.js·3d·three.js·热力图
golang学习记8 小时前
从0死磕全栈之Next.js 重定向全指南:从基础跳转到大规模路由迁移
前端
Mapmost8 小时前
Mapmost地图引擎丨测绘资质“合规门票”
前端
JarvanMo8 小时前
不要在 SwiftUI 中使用 .onAppear() 进行异步(Async)工作——这就是它导致你的 App 出现 Bug 的原因。
前端
Moment8 小时前
Next.js 16 新特性:如何启用 MCP 与 AI 助手协作 🤖🤖🤖
前端·javascript·node.js
吃饺子不吃馅8 小时前
Canvas高性能Table架构深度解析
前端·javascript·canvas
一枚前端小能手8 小时前
🔄 重学Vue之生命周期 - 从源码层面解析到实战应用的完整指南
前端·javascript·vue.js
JarvanMo8 小时前
Flutter:借助 jnigen通过原生互操作(Native Interop)使用 Android Intent。、
前端
开开心心就好8 小时前
Word转PDF工具,免费生成图片型文档
前端·网络·笔记·pdf·word·powerpoint·excel
一枚前端小能手8 小时前
「周更第9期」实用JS库推荐:mitt - 极致轻量的事件发射器深度解析
前端·javascript