诸位,各位靓仔靓女,欢迎来到老夫的 Vue 3 响应式副作用处理讲座!今天咱们不搞那些虚头巴脑的概念,直接上干货,聊聊 watch
和 watchEffect
这哥俩,在处理复杂场景,比如 API 请求和事件监听时,到底该咋用,才能让你的代码优雅又高效。
首先,咱们先来简单回顾一下 watch
和 watchEffect
的基本概念,毕竟温故而知新嘛。
watch
:精确监控,按需触发
watch
就像一个尽职尽责的保安,它会盯着你指定的特定目标(响应式数据),只有当这个目标发生变化时,它才会触发你设定的回调函数。这哥们儿非常精确,不会没事儿瞎触发,用起来也更加可控。
watchEffect
:自动追踪,立即执行
watchEffect
则像一个好奇宝宝,它会自己去探索你的回调函数中用到了哪些响应式数据,然后自动建立依赖关系。只要这些依赖的响应式数据发生变化,它就会毫不犹豫地执行回调函数。而且,这哥们儿一上来就会立即执行一次,先睹为快。
好了,概念复习完毕,下面咱们就开始进入实战环节,看看这哥俩在 API 请求和事件监听这些复杂场景中,到底该怎么耍。
场景一:API 请求
API 请求在前端开发中简直是家常便饭,而响应式地处理 API 请求更是 Vue 应用的必备技能。咱们先来看一个简单的例子:根据用户输入的搜索关键词,动态地请求搜索结果。
1. 使用 watch
实现
首先,咱们定义一个响应式的搜索关键词 keyword
,然后使用 watch
来监听它的变化,一旦 keyword
发生改变,就发起 API 请求。
<template>
<input type="text" v-model="keyword" placeholder="请输入关键词">
<ul>
<li v-for="result in searchResults" :key="result.id">{{ result.title }}</li>
</ul>
</template>
<script setup>
import { ref, watch } from 'vue';
const keyword = ref('');
const searchResults = ref([]);
watch(keyword, async (newKeyword, oldKeyword) => {
// 防抖处理,避免频繁请求
if (newKeyword === oldKeyword) return; // 初始值避免触发
if (!newKeyword.trim()) {
searchResults.value = []; //关键词为空时,清空结果
return;
}
try {
const response = await fetch(`/api/search?keyword=${newKeyword}`);
const data = await response.json();
searchResults.value = data.results;
} catch (error) {
console.error('搜索失败', error);
// 错误处理,例如显示错误提示
}
}, { immediate: false, deep: false }); // immediate: false 表示初始时不执行,deep: false 表示不深度监听对象内部属性
</script>
在这个例子中,我们使用 watch
监听 keyword
的变化,并在回调函数中发起 API 请求。为了避免用户频繁输入导致大量的请求,我们还加入了简单的防抖处理(当然,更专业的防抖可以使用 lodash
等库)。
注意几个点:
immediate: false
:设置immediate
为false
,可以避免在组件初始化时就发起一次不必要的请求。deep: false
:通常情况下,我们不需要深度监听keyword
内部的属性,所以设置deep
为false
可以提高性能。
2. 使用 watchEffect
实现
接下来,咱们看看使用 watchEffect
该怎么实现同样的功能。
<template>
<input type="text" v-model="keyword" placeholder="请输入关键词">
<ul>
<li v-for="result in searchResults" :key="result.id">{{ result.title }}</li>
</ul>
</template>
<script setup>
import { ref, watchEffect } from 'vue';
const keyword = ref('');
const searchResults = ref([]);
watchEffect(async () => {
if (!keyword.value.trim()) {
searchResults.value = [];
return;
}
try {
const response = await fetch(`/api/search?keyword=${keyword.value}`);
const data = await response.json();
searchResults.value = data.results;
} catch (error) {
console.error('搜索失败', error);
}
});
</script>
使用 watchEffect
的代码看起来更加简洁,因为它会自动追踪 keyword
的变化,并自动执行回调函数。
watch
vs watchEffect
:API 请求场景对比
特性 | watch |
watchEffect |
---|---|---|
目标 | 明确指定要监听的响应式数据 | 自动追踪回调函数中使用的响应式数据 |
触发时机 | 只有目标发生变化时才触发 | 依赖的响应式数据发生变化时触发,且初始时立即执行一次 |
代码简洁度 | 相对繁琐,需要手动指定监听目标和回调函数参数 | 更加简洁,自动追踪依赖 |
可控性 | 更高,可以精确控制触发时机和回调函数参数 | 相对较低,依赖关系由 Vue 自动管理 |
总结:
- 如果需要精确控制 API 请求的触发时机和参数,或者需要进行一些额外的逻辑处理(例如防抖、节流),那么
watch
是一个更好的选择。 - 如果只需要简单地根据响应式数据的变化发起 API 请求,并且不需要太多的控制,那么
watchEffect
可以让你的代码更加简洁。
场景二:事件监听
除了 API 请求,事件监听也是前端开发中常见的需求。咱们来看一个例子:监听窗口的滚动事件,并根据滚动距离动态地改变导航栏的样式。
1. 使用 watch
实现
<template>
<div class="navbar" :class="{ 'fixed': isFixed }">
导航栏
</div>
<div class="content" style="height: 2000px;">
滚动我
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
const isFixed = ref(false);
const scrollY = ref(0);
const handleScroll = () => {
scrollY.value = window.scrollY;
};
onMounted(() => {
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
watch(scrollY, (newScrollY) => {
isFixed.value = newScrollY > 100;
});
</script>
<style scoped>
.navbar {
background-color: #fff;
padding: 10px;
position: relative;
transition: all 0.3s ease;
}
.navbar.fixed {
position: fixed;
top: 0;
left: 0;
width: 100%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.content {
margin-top: 20px;
}
</style>
在这个例子中,我们首先使用 addEventListener
监听窗口的滚动事件,并在 handleScroll
函数中更新 scrollY
的值。然后,我们使用 watch
监听 scrollY
的变化,并根据滚动距离动态地设置 isFixed
的值,从而改变导航栏的样式。
2. 使用 watchEffect
实现
<template>
<div class="navbar" :class="{ 'fixed': isFixed }">
导航栏
</div>
<div class="content" style="height: 2000px;">
滚动我
</div>
</template>
<script setup>
import { ref, watchEffect, onMounted, onUnmounted } from 'vue';
const isFixed = ref(false);
const scrollY = ref(0);
const handleScroll = () => {
scrollY.value = window.scrollY;
};
onMounted(() => {
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
watchEffect(() => {
isFixed.value = scrollY.value > 100;
});
</script>
<style scoped>
.navbar {
background-color: #fff;
padding: 10px;
position: relative;
transition: all 0.3s ease;
}
.navbar.fixed {
position: fixed;
top: 0;
left: 0;
width: 100%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.content {
margin-top: 20px;
}
</style>
同样地,使用 watchEffect
的代码更加简洁,因为它会自动追踪 scrollY
的变化,并自动执行回调函数。
watch
vs watchEffect
:事件监听场景对比
特性 | watch |
watchEffect |
---|---|---|
目标 | 明确指定要监听的响应式数据 | 自动追踪回调函数中使用的响应式数据 |
触发时机 | 只有目标发生变化时才触发 | 依赖的响应式数据发生变化时触发,且初始时立即执行一次 |
代码简洁度 | 相对繁琐,需要手动指定监听目标和回调函数参数 | 更加简洁,自动追踪依赖 |
可控性 | 更高,可以精确控制触发时机和回调函数参数 | 相对较低,依赖关系由 Vue 自动管理 |
总结:
- 在事件监听场景中,
watch
和watchEffect
的选择也取决于你的需求。如果需要对事件进行一些额外的处理,或者需要精确控制回调函数的执行时机,那么watch
是一个更好的选择。 - 如果只需要简单地根据事件触发的响应式数据来更新组件的状态,那么
watchEffect
可以让你的代码更加简洁。
进阶技巧:watch
和 watchEffect
的高级用法
除了基本的用法之外,watch
和 watchEffect
还有一些高级用法,可以帮助你更好地处理复杂的响应式副作用。
1. flush: 'post'
默认情况下,watch
和 watchEffect
的回调函数会在 Vue 组件更新之前执行。如果你需要在组件更新之后执行回调函数,可以使用 flush: 'post'
选项。
<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
watch(count, (newCount) => {
console.log('count changed', newCount);
}, { flush: 'post' });
</script>
2. onInvalidate
watchEffect
提供了一个 onInvalidate
函数,可以在副作用函数即将重新执行时调用。这可以用来清理之前的副作用,例如取消未完成的 API 请求或移除事件监听器。
<script setup>
import { ref, watchEffect } from 'vue';
const keyword = ref('');
watchEffect((onInvalidate) => {
let timeoutId = setTimeout(() => {
console.log('搜索', keyword.value);
}, 500);
onInvalidate(() => {
clearTimeout(timeoutId);
console.log('取消之前的搜索');
});
});
</script>
在这个例子中,我们使用 onInvalidate
来清除之前的 setTimeout
定时器,从而实现防抖效果。
3. 停止 watchEffect
watchEffect
会返回一个停止函数,可以用来手动停止副作用的执行。
<script setup>
import { ref, watchEffect, onUnmounted } from 'vue';
const count = ref(0);
const stop = watchEffect(() => {
console.log('count', count.value);
});
onUnmounted(() => {
stop(); // 组件卸载时停止副作用
});
</script>
总结:选择合适的工具,事半功倍
watch
和 watchEffect
都是 Vue 3 中强大的响应式工具,可以帮助你处理各种复杂的副作用。选择哪个工具取决于你的具体需求。
场景 | 推荐工具 |
---|---|
精确控制触发时机 | watch ,可以精确指定监听的目标和回调函数参数,进行更细粒度的控制。 |
简单依赖追踪 | watchEffect ,自动追踪依赖,代码更加简洁。 |
需要清理副作用 | watchEffect ,配合 onInvalidate 可以方便地清理之前的副作用,例如取消 API 请求或移除事件监听器。 |
需要停止副作用的执行 | watchEffect ,返回的停止函数可以手动停止副作用的执行,避免不必要的资源消耗。 |
API 请求 | watch (需要精确控制,例如防抖、节流) 或 watchEffect (简单场景,只需要根据响应式数据变化发起请求)。 |
事件监听 | watch (需要对事件进行额外的处理) 或 watchEffect (简单场景,只需要根据事件触发的响应式数据来更新组件状态)。 |
记住,没有银弹!根据你的实际情况,选择最合适的工具,才能让你的代码更加清晰、高效。
好了,今天的讲座就到这里。希望大家能够掌握 watch
和 watchEffect
的精髓,在 Vue 3 的开发中游刃有余! 下次再见!