watchEffect

基本概念

watchEffect 会立即执行传入的函数,并响应式地追踪其依赖,当依赖发生变化时重新执行。

基本用法

import { ref, watchEffect } from 'vue'

const count = ref(0)
const name = ref('张三')

// 自动追踪依赖并立即执行
watchEffect(() => {
  console.log(`count: ${count.value}, name: ${name.value}`)
})
// 输出: count: 0, name: 张三

count.value++ // 输出: count: 1, name: 张三
name.value = '李四' // 输出: count: 1, name: 李四

停止监听

const stop = watchEffect(() => {
  console.log(count.value)
})

// 停止监听
stop()

清除副作用

watchEffect((onInvalidate) => {
  const timer = setTimeout(() => {
    console.log('定时器执行')
  }, 1000)
  
  // 在下次执行前或组件卸载时清除
  onInvalidate(() => {
    clearTimeout(timer)
  })
})

watch

基本概念

watch 需要明确指定监听的数据源,只有当数据源发生变化时才会执行回调函数。

监听单个响应式引用

import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})

count.value = 1 // 输出: count 从 0 变为 1

监听多个数据源

const firstName = ref('张')
const lastName = ref('三')

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`姓名从 ${oldFirst}${oldLast} 变为 ${newFirst}${newLast}`)
})

监听响应式对象

import { reactive, watch } from 'vue'

const user = reactive({ name: '张三', age: 25 })

// 监听整个对象
watch(user, (newUser, oldUser) => {
  console.log('用户信息发生变化', newUser)
}, { deep: true })

// 监听对象的特定属性
watch(() => user.name, (newName, oldName) => {
  console.log(`姓名从 ${oldName} 变为 ${newName}`)
})

watch 配置选项

watch(
  source,
  callback,
  {
    immediate: true,    // 立即执行一次
    deep: true,        // 深度监听
    flush: 'post'      // 回调执行时机
  }
)

在 Vue 3 中,watch 监听对象和对象属性有很大的区别,让我详细解释一下:

1. 监听整个对象

使用 reactive 对象

import { reactive, watch } from 'vue'

const user = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京',
    street: '朝阳区'
  }
})

// 监听整个对象 - 默认是深度监听
watch(user, (newUser, oldUser) => {
  console.log('用户对象发生变化')
  console.log('新值:', newUser)
  console.log('旧值:', oldUser)
  // 注意:newUser 和 oldUser 指向同一个对象!
})

user.name = '李四'     // 触发监听
user.age = 26         // 触发监听
user.address.city = '上海' // 触发监听

重要特点:

  • reactive 对象的监听默认是深度的
  • newUseroldUser 实际上是同一个对象引用
  • 任何嵌套属性的变化都会触发监听

使用 ref 包装的对象

import { ref, watch } from 'vue'

const user = ref({
  name: '张三',
  age: 25
})

// 需要显式开启深度监听
watch(user, (newUser, oldUser) => {
  console.log('用户对象发生变化')
}, { deep: true }) // 必须设置 deep: true

// 或者监听 .value
watch(user.value, (newUser, oldUser) => {
  console.log('用户对象发生变化')
})

user.value.name = '李四' // 触发监听

2. 监听对象的特定属性

使用 getter 函数

const user = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京',
    street: '朝阳区'
  }
})

// 监听单个属性
watch(() => user.name, (newName, oldName) => {
  console.log(`姓名从 ${oldName} 变为 ${newName}`)
})

// 监听嵌套属性
watch(() => user.address.city, (newCity, oldCity) => {
  console.log(`城市从 ${oldCity} 变为 ${newCity}`)
})

// 监听嵌套对象(需要深度监听)
watch(() => user.address, (newAddress, oldAddress) => {
  console.log('地址对象发生变化')
}, { deep: true })

user.name = '李四'           // 只触发 name 的监听
user.address.city = '上海'   // 只触发 city 的监听
user.age = 26               // 不触发任何上述监听

3. 主要区别总结

监听方式语法触发条件新旧值性能
整个对象watch(obj, callback)对象任何属性变化同一个对象引用较低(监听所有属性)
特定属性watch(() => obj.prop, callback)只有该属性变化真实的新旧值较高(只监听指定属性)

4. 实际应用场景对比

监听整个对象的场景

// 表单数据整体验证
const formData = reactive({
  username: '',
  email: '',
  password: ''
})

watch(formData, (newData) => {
  // 任何表单字段变化都重新验证整个表单
  validateForm(newData)
})

监听特定属性的场景

// 只关心特定字段的变化
watch(() => formData.email, (newEmail) => {
  // 只有邮箱变化时才验证邮箱格式
  validateEmail(newEmail)
})

watch(() => formData.username, (newUsername) => {
  // 只有用户名变化时才检查是否重复
  checkUsernameAvailability(newUsername)
})

5. 监听多个属性

// 监听多个特定属性
watch(
  [() => user.name, () => user.age],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log(`姓名: ${oldName} -> ${newName}`)
    console.log(`年龄: ${oldAge} -> ${newAge}`)
  }
)

// 监听对象的多个嵌套属性
watch(
  [() => user.address.city, () => user.address.street],
  ([newCity, newStreet], [oldCity, oldStreet]) => {
    console.log('地址信息发生变化')
  }
)

6. 性能优化建议

//  性能较差 - 监听整个对象
watch(largeObject, (newVal) => {
  // 任何属性变化都会触发
  updateUI()
})

//  性能较好 - 只监听需要的属性
watch(() => largeObject.importantProperty, (newVal) => {
  // 只有重要属性变化才触发
  updateUI()
})

//  按需监听多个属性
watch(
  [() => obj.prop1, () => obj.prop2],
  ([newProp1, newProp2]) => {
    // 只有这两个属性变化才触发
    updateSpecificUI(newProp1, newProp2)
  }
)

7. 注意事项

const user = reactive({
  profile: { name: '张三' }
})

// 监听嵌套对象属性时的陷阱
watch(() => user.profile, (newProfile, oldProfile) => {
  // newProfile 和 oldProfile 是同一个对象!
  console.log(newProfile === oldProfile) // true
}, { deep: true })

// 如果需要真正的新旧值对比,监听具体属性
watch(() => user.profile.name, (newName, oldName) => {
  // 这里的 newName 和 oldName 是不同的值
  console.log(newName === oldName) // false
})

总的来说,watchEffect 更适合简单的副作用操作和自动依赖收集,而 watch 更适合需要精确控制监听目标和获取新旧值的场景。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]