前言
重要提示:文章只适合初学者,不适合专家!!!
Vue3中的watch:数据变化的"眼睛"和"耳朵"
想象你有一个超级助理,每当商店里的商品价格发生变化,它就会立即通知你。在Vue3中,watch就是这个聪明的小助理!
一、为什么需要watch?数据变化的侦探
在日常开发中,你会经常遇到这些情况:
- 用户输入搜索词时,自动开始搜索
- 表单数据变化时,自动保存草稿
- 路由参数变化时,重新加载数据
- 当选择地区变化时,重新获取天气数据
<!-- 问题:如何知道searchText变化了? -->
<input v-model="searchText">
计算属性不够用的情况:
- 当数据变化需要执行异步操作(API请求)
- 当数据变化需要执行复杂操作(操作DOM、播放声音等)
- 当需要知道新值和旧值的变化情况时
这就是watch的用武之地!
二、watch是什么?Vue的数据监控专家
watch
是Vue3提供的一个核心函数,用于监听响应式数据的变化。当数据变化时,watch会执行你指定的回调函数。
watch的特点:
- 🔍 侦听特定数据源的变化
- 📡 支持异步操作和复杂逻辑
- 📊 可以同时获取新旧值
- ⏲ 可以控制何时触发监听
三、watch使用三步骤
步骤1:导入watch
import { ref, watch } from 'vue'
步骤2:设置要监听的数据
const searchText = ref('')
const user = ref({
name: '张三',
age: 25
})
步骤3:使用watch进行监听
// 监听单个ref
watch(searchText, (newValue, oldValue) => {
console.log(`搜索词从"${oldValue}"变为"${newValue}"`)
// 这里可以执行搜索API调用
})
// 监听对象中的特定属性
watch(
() => user.value.age,
(newAge, oldAge) => {
console.log(`年龄从${oldAge}岁变为${newAge}岁`)
}
)
// 监听多个数据源
watch(
[() => user.value.name, () => user.value.age],
([newName, newAge], [oldName, oldAge]) => {
console.log(`用户信息变更:姓名从${oldName}变为${newName},年龄从${oldAge}变为${newAge}`)
}
)
四、完整示例:实时搜索功能
<template>
<div class="search-container">
<input
v-model="searchText"
placeholder="输入搜索内容..."
/>
<p v-if="isLoading">搜索中...</p>
<ul v-else>
<li v-for="result in results" :key="result.id">
{{ result.title }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 响应式数据
const searchText = ref('')
const isLoading = ref(false)
const results = ref([])
// 使用watch监听搜索词的变化
watch(searchText, async (newValue, oldValue) => {
if (newValue.trim() === '') {
results.value = []
return
}
try {
isLoading.value = true
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 500))
// 实际项目中这里会是API调用
results.value = [
{ id: 1, title: `"${newValue}"的结果1` },
{ id: 2, title: `"${newValue}"的结果2` }
]
} finally {
isLoading.value = false
}
})
</script>
<style>
.search-container {
max-width: 500px;
margin: 20px auto;
}
input {
width: 100%;
padding: 10px;
font-size: 16px;
}
ul {
list-style: none;
padding: 0;
margin-top: 10px;
}
li {
padding: 8px;
border-bottom: 1px solid #eee;
}
</style>
五、watch高级用法:做更精细的控制
1. 立即执行:初始化时就运行一次
// 设置{ immediate: true }选项
watch(searchText, () => {
// 会在初始化时立即执行一次
}, { immediate: true })
2. 深度监听:看透对象内部
const user = ref({
name: '李四',
address: {
city: '北京'
}
})
// 监听整个对象(自动深度监听)
watch(user, (newUser) => {
console.log('用户信息变化了', newUser)
}, { deep: true })
// 等价于
watch(user, (newUser) => {
console.log('用户信息变化了', newUser)
}) // 对于reactive对象,deep默认开启
3. 回调触发时机:在DOM更新后执行
watch(
searchText,
() => {
// DOM已经更新完毕
console.log('DOM已更新')
},
{ flush: 'post' } // post表示在DOM更新后执行
)
4. 停止监听:不需要时关闭它
// watch函数返回停止函数
const stop = watch(searchText, () => {
// 监听逻辑...
})
// 当不再需要时调用停止函数
stop()
六、常见问题及解决方案
问题1:为什么watch有时不触发?
原因:监听的数据不是响应式的
解决:
// ❌ 错误:普通对象没有响应性
const normalObject = { count: 0 }
watch(normalObject.count, callback)
// ✅ 正确:使用ref或reactive
const count = ref(0)
watch(count, callback)
问题2:watch内修改数据导致无限循环?
const count = ref(0)
// ❌ 危险:可能会导致无限循环
watch(count, (newVal) => {
count.value = newVal + 1
})
// ✅ 正确:添加终止条件
watch(count, (newVal) => {
if (newVal < 10) {
count.value = newVal + 1
}
})
问题3:新值旧值相同?
原因:当监听数组或对象时
解决:使用深拷贝
watch(
() => [...items.value], // 数组深拷贝
(newItems, oldItems) => {
// 现在可以正确比较了
},
{ deep: true }
)
七、watch vs watchEffect:双胞胎的区别
特性 | watch | watchEffect |
---|---|---|
监听方式 | 需要明确指定数据源 | 自动追踪依赖 |
首次执行 | 默认不立即执行(可配置) | 立即执行 |
新/旧值 | 可获取新旧值 | 不可获取旧值 |
适用场景 | 精确控制监听源 | 无需源立即响应依赖变化 |
复杂程度 | 代码量稍多 | 代码更简洁 |
// watchEffect示例:自动追踪依赖
const searchText = ref('')
const isLoading = ref(false)
watchEffect(async () => {
// 自动追踪searchText.value和isLoading.value依赖
if (searchText.value === '') return
try {
isLoading.value = true
// 执行搜索...
} finally {
isLoading.value = false
}
})
八、watch最佳实践指南
1. 避免过度使用
应该优先考虑:
- 模板表达式:用于简单显示逻辑
- 计算属性:用于数据转换和衍生值
2. 内存泄漏防护
import { onUnmounted } from 'vue'
setup() {
const stop = watch(/* ... */)
// 组件卸载时停止监听
onUnmounted(() => {
stop()
})
}
九、总结:什么时候使用watch?
请记住这个口诀:
数据变,要看值;异步操作用watch;
新值旧值都要知,深度变化都能视;
立即执行需要时,停止监听勿忘记;
避免无限循环圈,合理使用效率提。
在以下场景优先选择watch:
- 需要获取变化前后的值时
- 需要控制何时触发监听时
- 需要监听指定数据源而非依赖时
- 需要在数据变化后执行异步操作时
- 需要精细控制深度监听行为时