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(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(user, (newValue, oldValue) => {
console.log('User changed:', oldValue, '->', newValue)
}, { deep: true })
watchEffect(() => {
console.log('User city is:', user.value.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(count, (newValue, oldValue) => {
console.log('Count changed:', oldValue, '->', newValue)
})
watch(count, (newValue, oldValue) => {
console.log('Count immediate:', oldValue, '->', newValue)
}, { immediate: true })
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(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((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
- 需要比较变化前后的值
- 需要监听特定的数据源
- 需要执行异步操作
- 需要控制回调函数的触发时机
4.2 何时使用 watchEffect
- 需要在组件初始化时就执行数据监听
- 需要自动收集依赖
- 不需要比较变化前后的值
- 监听逻辑相对简单
4.3 注意事项
- 避免在
watch/watchEffect
中修改被监听的值,否则可能导致无限循环 - 合理使用清理函数,避免内存泄漏
watch
默认不深度监听且不会立即执行,需要配合 immediate
和 deep
选项使用