概述
Vue 3.5 在响应式系统方面进行了重大改进,特别是在 watch
API 方面。这些更新提供了更好的性能、更灵活的配置选项和更强大的功能。
主要更新内容
新的 watch 选项
flush
选项增强
- 新增
pre
和post
选项 - 更好的控制执行时机
deep
选项优化
- 更智能的深度监听
- 减少不必要的递归
immediate
选项改进
- 更可靠的立即执行
- 更好的错误处理
性能优化
更高效的依赖追踪
- 减少不必要的响应式更新
- 更精确的依赖收集
内存使用优化
- 更好的垃圾回收
- 减少内存泄漏风险
新的 API 功能
watchEffect
增强
- 更灵活的清理函数
- 更好的错误边界处理
watchPostEffect
和 watchSyncEffect
- 新增的专用 watch 函数
- 更精确的执行时机控制
详细示例
基础监听
<template>
<div
class="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-400 to-pink-600 p-5"
>
<div class="bg-white rounded-2xl p-8 shadow-2xl max-w-md w-full">
<div class="mb-6">
<label class="block text-gray-600 font-medium mb-2">输入消息:</label>
<input
v-model="message"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg text-base transition-all duration-300 focus:outline-none focus:border-purple-400 focus:ring-4 focus:ring-purple-100"
placeholder="请输入消息..."
/>
</div>
<div class="mb-6 p-4 bg-gray-50 rounded-lg">
<p class="text-gray-600 mb-2">
消息:
<span class="text-purple-500 font-semibold">{{
message || "暂无"
}}</span>
</p>
<p class="text-gray-600">
计数: <span class="text-purple-500 font-semibold">{{ count }}</span>
</p>
</div>
<button
@click="increment"
class="w-full py-3 px-6 bg-gradient-to-r from-purple-400 to-pink-500 text-white font-medium rounded-lg text-base cursor-pointer transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg active:translate-y-0"
>
增加计数
</button>
</div>
</div>
</template>
<script setup>
import { ref, watch, watchEffect } from "vue";
const message = ref("");
const count = ref(0);
// 基础 watch
watch(message, (newValue, oldValue) => {
console.log("消息变化:", oldValue, "->", newValue);
});
// 深度监听对象
const user = ref({
name: "张三",
age: 25,
});
watch(
user,
(newUser, oldUser) => {
console.log("用户信息更新:", newUser);
},
{ deep: true }
);
// 立即执行
watch(
count,
(newCount) => {
console.log("计数更新为:", newCount);
},
{ immediate: true }
);
// watchEffect 示例
watchEffect(() => {
console.log("当前消息和计数:", message.value, count.value);
});
const increment = () => {
count.value++;
};
</script>
高级用法
<template>
<div class="max-w-2xl mx-auto p-5 font-sans">
<div class="mb-6">
<input
v-model="searchQuery"
placeholder="搜索水果..."
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg text-base transition-all duration-300 bg-white shadow-sm focus:outline-none focus:border-blue-500 focus:shadow-md focus:shadow-blue-100 placeholder-gray-400"
/>
</div>
<div v-if="loading" class="flex flex-col items-center py-10">
<div
class="w-10 h-10 border-3 border-gray-100 border-t-blue-500 rounded-full animate-spin mb-4"
></div>
<p class="text-gray-500 text-sm m-0">加载中...</p>
</div>
<ul v-else class="list-none p-0 m-0 grid gap-3">
<li
v-for="item in filteredItems"
:key="item.id"
class="bg-white border border-gray-200 rounded-lg p-4 transition-all duration-200 cursor-pointer shadow-sm hover:-translate-y-0.5 hover:shadow-lg hover:border-blue-500"
>
<span class="text-base font-medium text-gray-800">{{ item.name }}</span>
</li>
</ul>
<div
v-if="!loading && filteredItems.length === 0"
class="text-center py-10 text-gray-500 text-base"
>
<p>没有找到匹配的水果</p>
</div>
</div>
</template>
<script setup>
import { ref, watch, watchEffect, computed } from "vue";
const searchQuery = ref("");
const items = ref([
{ id: 1, name: "苹果" },
{ id: 2, name: "香蕉" },
{ id: 3, name: "橙子" },
]);
const loading = ref(false);
// 使用 flush: 'post' 确保 DOM 更新后执行
watch(
searchQuery,
async (newQuery) => {
if (newQuery.trim()) {
loading.value = true;
try {
// 模拟异步搜索
await new Promise((resolve) => setTimeout(resolve, 500));
console.log("搜索:", newQuery);
} finally {
loading.value = false;
}
}
},
{ flush: "post" }
);
// 使用 watchEffect 进行响应式计算
const filteredItems = computed(() => {
if (!searchQuery.value.trim()) return items.value;
return items.value.filter((item) =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
// 清理函数示例
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log("延迟执行");
}, 1000);
onCleanup(() => {
clearTimeout(timer);
});
});
</script>
监听多个源
<script setup>
import { ref, watch } from "vue";
const firstName = ref("");
const lastName = ref("");
// 监听多个响应式源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log("姓名变化:", `${oldFirst} ${oldLast} -> ${newFirst} ${newLast}`);
});
// 使用 getter 函数
watch(
() => [firstName.value, lastName.value],
([newFirst, newLast]) => {
console.log("完整姓名:", `${newFirst} ${newLast}`);
}
);
</script>
<template>
<div>
<input v-model="firstName" placeholder="请输入名字" />
<input v-model="lastName" placeholder="请输入姓氏" />
</div>
</template>
异步监听
<script setup>
import { ref, watch } from "vue";
const data = ref(null);
const error = ref(null);
// 异步 watch
watch(data, async (newData) => {
if (newData) {
try {
error.value = null;
// 处理数据
await processData(newData);
} catch (err) {
error.value = err.message;
}
}
});
async function processData(data) {
// 模拟异步处理
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("数据处理完成:", data);
}
</script>
最佳实践
选择合适的监听方式
// 对于简单的响应式更新,使用 watchEffect
watchEffect(() => {
console.log("自动追踪依赖:", count.value);
});
// 对于需要精确控制的场景,使用 watch
watch(count, (newCount, oldCount) => {
console.log("精确控制:", oldCount, "->", newCount);
});
性能优化
// 使用 deep 选项时要谨慎
watch(object, callback, { deep: true });
// 对于大型对象,考虑使用 getter
watch(() => object.value.specificProperty, callback);
错误处理
watch(data, async (newData) => {
try {
await processData(newData);
} catch (error) {
console.error("处理数据时出错:", error);
// 处理错误
}
});
清理资源
watchEffect((onCleanup) => {
const subscription = subscribe();
onCleanup(() => {
subscription.unsubscribe();
});
});
迁移指南
从 Vue 2 迁移
// Vue 2 写法
watch: {
message(newVal, oldVal) {
console.log("消息变化:", oldVal, "->", newVal);
}
}
// Vue 3.5 写法
watch(message, (newVal, oldVal) => {
console.log("消息变化:", oldVal, "->", newVal);
});
从 Vue 3.0 迁移
// Vue 3.0 写法
watch(source, callback, { flush: "pre" });
// Vue 3.5 写法 - 使用新的 flush 选项
watch(source, callback, { flush: "post" });
使用建议
选择合适的监听方式
// 简单响应式更新
watchEffect(() => {
console.log("自动追踪:", value.value);
});
// 需要精确控制
watch(source, callback, { flush: "post" });
性能优化
// 避免深度监听大型对象
watch(() => object.value.specificProperty, callback);
// 使用清理函数
watchEffect((onCleanup) => {
const subscription = subscribe();
onCleanup(() => subscription.unsubscribe());
});
错误处理
watch(data, async (newData) => {
try {
await processData(newData);
} catch (error) {
console.error("处理失败:", error);
}
});
注意事项
内存管理: 确保在组件卸载时正确清理 watch
性能考虑: 避免在 watch 中执行昂贵的操作
循环依赖: 注意避免在 watch 回调中修改被监听的响应式数据
异步操作: 正确处理异步 watch 中的错误和清理
总结
Vue 3.5 的 watch 更新带来了:
✅ 更好的性能表现: 更高效的依赖追踪和内存优化
✅ 更灵活的配置选项: 新的 flush 选项和增强的 immediate 选项
✅ 更强大的错误处理: 更好的错误边界处理和清理机制
✅ 更清晰的 API 设计: 新增的 watchPostEffect 和 watchSyncEffect
✅ 更好的开发体验: 更智能的深度监听和更可靠的立即执行
这些改进使得 Vue 的响应式系统更加健壮和易用,为开发者提供了更好的工具来处理复杂的响应式逻辑。
深入解析 Vue 3.5 Watch 新特性:从基础到高级应用 - 高质量源码分享平台-免费下载各类网站源码与模板及前沿技术分享