Vue 3 中 watch 的用法详解

151 阅读5分钟

目录

基础用法

监听单个响应式引用

import { ref, watch } from 'vue'

const count = ref(0)

// 监听单个响应式引用
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

// 修改值触发监听
count.value = 1 // 输出: count changed from 0 to 1

监听计算属性

import { ref, computed, watch } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed(() => `${firstName.value} ${lastName.value}`)

watch(fullName, (newName, oldName) => {
  console.log(`Full name changed from ${oldName} to ${newName}`)
})

监听多个数据源

import { ref, watch } from 'vue'

const firstName = ref('')
const lastName = ref('')
const age = ref(0)

// 监听多个响应式引用
watch([firstName, lastName, age], ([newFirst, newLast, newAge], [oldFirst, oldLast, oldAge]) => {
  console.log('Multiple values changed:', {
    firstName: { old: oldFirst, new: newFirst },
    lastName: { old: oldLast, new: newLast },
    age: { old: oldAge, new: newAge }
  })
})

监听对象属性

监听对象的特定属性

import { ref, watch } from 'vue'

const user = ref({ 
  name: 'John', 
  age: 25, 
  address: { city: 'Beijing', country: 'China' }
})

// 监听对象的特定属性
watch(() => user.value.name, (newName, oldName) => {
  console.log(`Name changed from ${oldName} to ${newName}`)
})

// 监听嵌套对象属性
watch(() => user.value.address.city, (newCity, oldCity) => {
  console.log(`City changed from ${oldCity} to ${newCity}`)
})

深度监听整个对象

// 深度监听整个对象
watch(user, (newUser, oldUser) => {
  console.log('User object changed:', newUser)
}, { deep: true })

// 或者使用 getter 函数
watch(() => user.value, (newUser, oldUser) => {
  console.log('User object changed:', newUser)
}, { deep: true })

监听路由变化

import { watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 监听路由变化
watch(
  () => router.currentRoute.value,
  (newRoute, oldRoute) => {
    console.log('Route changed:', {
      from: oldRoute?.path,
      to: newRoute.path,
      query: newRoute.query,
      params: newRoute.params
    })
  },
  { immediate: true } // 立即执行一次
)

// 监听路由查询参数
watch(
  () => route.query,
  (newQuery, oldQuery) => {
    console.log('Query params changed:', newQuery)
  },
  { deep: true }
)

配置选项

watch(
  source,           // 要监听的数据源
  callback,         // 回调函数
  {
    immediate: true,    // 立即执行一次回调
    deep: true,         // 深度监听对象/数组
    flush: 'post',      // 执行时机:'pre' | 'sync' | 'post'
    onTrack(e) {        // 调试:依赖被追踪时调用
      console.log('Dependency tracked:', e)
    },
    onTrigger(e) {      // 调试:依赖变化时调用
      console.log('Dependency triggered:', e)
    }
  }
)

flush 选项详解

// 'pre' - 在组件更新前执行
watch(count, callback, { flush: 'pre' })

// 'sync' - 同步执行
watch(count, callback, { flush: 'sync' })

// 'post' - 在组件更新后执行(默认)
watch(count, callback, { flush: 'post' })

实际应用示例

1. 表单数据监听

const formData = ref({
  username: '',
  password: '',
  email: '',
  phone: ''
})

// 监听表单数据变化
watch(formData, (newForm) => {
  // 表单验证
  validateForm(newForm)
  // 自动保存草稿
  saveDraft(newForm)
}, { deep: true })

// 监听特定字段
watch(() => formData.value.email, (newEmail) => {
  if (newEmail) {
    validateEmail(newEmail)
  }
})

2. 用户权限监听

const userRole = ref('guest')
const permissions = ref([])

watch(userRole, (newRole) => {
  // 根据角色更新权限
  updatePermissions(newRole)
  // 更新菜单显示
  updateMenuVisibility(newRole)
})

// 监听权限变化
watch(permissions, (newPermissions) => {
  // 更新按钮状态
  updateButtonStates(newPermissions)
}, { deep: true })

3. 搜索功能

const searchKeyword = ref('')
const searchResults = ref([])

// 防抖搜索
const debouncedSearch = debounce(async (keyword) => {
  if (keyword.length > 2) {
    const results = await searchAPI(keyword)
    searchResults.value = results
  }
}, 300)

watch(searchKeyword, (newKeyword) => {
  debouncedSearch(newKeyword)
})

4. 响应式布局

const windowWidth = ref(window.innerWidth)
const isMobile = ref(false)

watch(windowWidth, (newWidth) => {
  isMobile.value = newWidth < 768
  // 调整布局
  adjustLayout(newWidth)
})

// 监听窗口大小变化
window.addEventListener('resize', () => {
  windowWidth.value = window.innerWidth
})

5. 数据同步

const localData = ref({})
const serverData = ref({})

// 监听本地数据变化,同步到服务器
watch(localData, async (newData) => {
  try {
    await syncToServer(newData)
  } catch (error) {
    console.error('Sync failed:', error)
  }
}, { deep: true })

// 监听服务器数据变化,更新本地数据
watch(serverData, (newData) => {
  localData.value = { ...newData }
}, { deep: true })

watchEffect 对比

import { watchEffect } from 'vue'

// watchEffect 会自动追踪依赖,无需显式指定
watchEffect(() => {
  console.log(`Count is: ${count.value}`)
  console.log(`Name is: ${user.value.name}`)
  // 自动追踪 count 和 user.name 的变化
})

// 对比 watch
watch([count, () => user.value.name], ([newCount, newName]) => {
  console.log(`Count is: ${newCount}`)
  console.log(`Name is: ${newName}`)
})

watchEffect 的优势

  • 自动追踪依赖
  • 代码更简洁
  • 适合副作用操作

watch 的优势

  • 可以获取旧值
  • 更精确的控制
  • 可以监听多个不相关的数据源

停止监听

// 获取停止函数
const stopWatcher = watch(count, (newValue) => {
  console.log(newValue)
})

// 停止监听
stopWatcher()

// 在组件卸载时自动停止
onUnmounted(() => {
  stopWatcher()
})

条件监听

const shouldWatch = ref(true)

const stopWatcher = watch(
  count,
  (newValue) => {
    console.log(newValue)
  },
  { immediate: true }
)

// 根据条件停止监听
watch(shouldWatch, (should) => {
  if (!should) {
    stopWatcher()
  }
})

最佳实践

1. 性能优化

// ❌ 避免深度监听大型对象
watch(largeObject, callback, { deep: true })

// ✅ 监听特定属性
watch(() => largeObject.value.importantField, callback)

// ✅ 使用 shallowRef 避免深度监听
const shallowData = shallowRef({ count: 0 })
watch(shallowData, callback) // 不会深度监听

2. 内存管理

// 在组件卸载时清理
onUnmounted(() => {
  // 停止所有监听器
  stopWatchers.forEach(stop => stop())
})

3. 防抖和节流

import { debounce, throttle } from 'lodash-es'

// 防抖处理
const debouncedCallback = debounce((value) => {
  // 处理逻辑
}, 300)

watch(inputValue, debouncedCallback)

// 节流处理
const throttledCallback = throttle((value) => {
  // 处理逻辑
}, 1000)

watch(scrollPosition, throttledCallback)

4. 条件执行

watch(
  data,
  (newData) => {
    // 只在满足条件时执行
    if (newData.length > 0) {
      processData(newData)
    }
  }
)

5. 错误处理

watch(
  asyncData,
  async (newData) => {
    try {
      await processAsyncData(newData)
    } catch (error) {
      console.error('Processing failed:', error)
      // 错误处理逻辑
    }
  }
)

常见问题

1. 为什么 watch 不触发?

// ❌ 监听非响应式数据
const normalObject = { count: 0 }
watch(normalObject, callback) // 不会触发

// ✅ 使用响应式数据
const reactiveObject = ref({ count: 0 })
watch(reactiveObject, callback, { deep: true })

2. 深度监听的性能问题

// ❌ 深度监听大型对象
watch(largeObject, callback, { deep: true })

// ✅ 监听特定属性
watch(() => largeObject.value.specificField, callback)

// ✅ 使用 watchEffect 自动优化
watchEffect(() => {
  // 只追踪实际使用的属性
  console.log(largeObject.value.specificField)
})

3. 异步操作的处理

// ✅ 正确处理异步操作
watch(
  searchKeyword,
  async (newKeyword) => {
    if (newKeyword) {
      try {
        const results = await searchAPI(newKeyword)
        searchResults.value = results
      } catch (error) {
        console.error('Search failed:', error)
      }
    }
  }
)

4. 避免无限循环

// ❌ 可能导致无限循环
watch(data, (newData) => {
  data.value = processData(newData) // 修改被监听的数据
})

// ✅ 使用不同的变量
const processedData = ref({})
watch(data, (newData) => {
  processedData.value = processData(newData)
})

总结

watch 是 Vue 3 中非常重要的响应式 API,它提供了强大的数据监听能力。合理使用 watch 可以:

  • 实现数据的响应式更新
  • 处理副作用操作
  • 实现组件间的数据同步
  • 优化应用性能

记住要根据具体场景选择合适的监听方式,并注意性能优化和内存管理。