Vue3 中 watch 和 watchEffect 的区别详解

1. 基本概念

1.1 watch

  • 需要明确指定监听的响应式引用
  • 可以访问监听状态的新值和旧值
  • 可以监听多个数据源
  • 更具有针对性,只在指定的数据源发生变化时才触发回调

1.2 watchEffect

  • 自动追踪响应式依赖
  • 立即执行,不需要指定监听的数据源
  • 不能访问变化前的值
  • 更简洁,但可能会监听到不需要的数据变化

2. 基础示例

2.1 watch 基础用法

<template>
  <div>
    <input v-model="firstName" placeholder="First Name">
    <input v-model="lastName" placeholder="Last Name">
    <p>Full Name: {{ fullName }}</p>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const firstName = ref('')
const lastName = ref('')
const fullName = ref('')

// 监听单个数据源
watch(firstName, (newValue, oldValue) => {
  console.log('firstName changed:', oldValue, '->', newValue)
  updateFullName()
})

// 监听多个数据源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log('name changed:', {
    first: `${oldFirst} -> ${newFirst}`,
    last: `${oldLast} -> ${newLast}`
  })
  fullName.value = `${newFirst} ${newLast}`.trim()
})

const updateFullName = () => {
  fullName.value = `${firstName.value} ${lastName.value}`.trim()
}
</script>

2.2 watchEffect 基础用法

<template>
  <div>
    <input v-model="searchQuery" placeholder="Search...">
    <p>Results: {{ results.length }}</p>
    <ul>
      <li v-for="result in results" :key="result.id">{{ result.title }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue'

const searchQuery = ref('')
const results = ref([])

// watchEffect 会自动追踪内部使用的响应式引用
watchEffect(async () => {
  if (searchQuery.value.length > 2) {
    const response = await fetch(`/api/search?q=${searchQuery.value}`)
    results.value = await response.json()
  } else {
    results.value = []
  }
})
</script>

3. 高级用法对比

3.1 深度监听

<script setup>
import { ref, watch, watchEffect } from 'vue'

const user = ref({
  name: 'John',
  address: {
    city: 'New York',
    street: 'Broadway'
  }
})

// watch 深度监听
watch(user, (newValue, oldValue) => {
  console.log('User changed:', oldValue, '->', newValue)
}, { deep: true })

// watchEffect 自动深度监听
watchEffect(() => {
  console.log('User city is:', user.value.address.city)
  // 会在 user.address.city 变化时自动触发
})

// 修改嵌套属性
setTimeout(() => {
  user.value.address.city = 'Los Angeles'
}, 1000)
</script>

3.2 立即执行

<script setup>
import { ref, watch, watchEffect } from 'vue'

const count = ref(0)

// watch 默认不会立即执行
watch(count, (newValue, oldValue) => {
  console.log('Count changed:', oldValue, '->', newValue)
})

// watch 立即执行
watch(count, (newValue, oldValue) => {
  console.log('Count immediate:', oldValue, '->', newValue)
}, { immediate: true })

// watchEffect 总是立即执行
watchEffect(() => {
  console.log('Count is:', count.value)
})

// 修改值
setTimeout(() => {
  count.value++
}, 1000)
</script>

3.3 清理副作用

<script setup>
import { ref, watch, watchEffect } from 'vue'

const userId = ref(1)

// watch 清理副作用
watch(userId, (newId, oldId, onCleanup) => {
  const controller = new AbortController()
  const signal = controller.signal
  
  fetch(`/api/user/${newId}`, { signal })
    .then(response => response.json())
    .then(data => {
      console.log('User data:', data)
    })
  
  onCleanup(() => {
    // 在下一次执行前取消上一次的请求
    controller.abort()
  })
})

// watchEffect 清理副作用
watchEffect((onCleanup) => {
  const controller = new AbortController()
  const signal = controller.signal
  
  fetch(`/api/user/${userId.value}`, { signal })
    .then(response => response.json())
    .then(data => {
      console.log('User data:', data)
    })
  
  onCleanup(() => controller.abort())
})
</script>

4. 总结

4.1 何时使用 watch

  1. 需要比较变化前后的值
  2. 需要监听特定的数据源
  3. 需要执行异步操作
  4. 需要控制回调函数的触发时机

4.2 何时使用 watchEffect

  1. 需要在组件初始化时就执行数据监听
  2. 需要自动收集依赖
  3. 不需要比较变化前后的值
  4. 监听逻辑相对简单

4.3 注意事项

  1. 避免在 watch/watchEffect 中修改被监听的值,否则可能导致无限循环
  2. 合理使用清理函数,避免内存泄漏
  3. watch 默认不深度监听且不会立即执行,需要配合 immediatedeep 选项使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值