1. 场景
有一天你心血来潮用vue3写了一个输入框,是这样的
<script setup>
import { ref, watchEffect, reactive } from 'vue'
const object = reactive({ // 响应式对象 object
id: 1,
name: 'wsw'
});
// 输入处理事件
function handleInput(e) {
console.log('@@@', e.target.value);
}
</script>
<template>
<div>
姓名:<input v-model="object.name" @input='handleInput'/> <!-- 双向绑定 -->
</div>
</template>
每次在输入/删除一个字符都会触发handleInput
事件,打印出e.target.value
领导看完你的代码后,摇头说这样不行,频繁触发事件性能会下降,让你写防抖。
2. “防抖”概念
所谓“防抖”,就是**指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。**拿上面的例子来说,我们在持续输入的时候,假设防抖函数隔0.5秒再去执行打印输出,如果在这0.5秒内我们还在输入,那就会重新计时,直到我们在0.5秒内没有再输入内容。
这也符合逻辑,当我们在一个搜索框中输入要搜索的内容时,我们并不需要输入框在蹦进去每个字的时候都去做处理,一般我们希望输入完后最终结果能够被成功获取即可。那么类似输入框这样的控件是否有自带的能够判断用户是否输入结束的方法或属性呢?
答案是没有的。这并不现实,我们可能输一会停一会,输入框不可能认为你某个时候已经输入完了然后擅作主张给你做处理(注意区别blur事件,blur需要有失焦操作)。
所以需要我们手动去实现“防抖”,前面说到了计时,因此可以用计时器setTimeout
来控制触发时机。
3. “防抖”实现
3.1 基本写法
<script setup>
import { reactive } from 'vue'
const object = reactive({
id: 1,
name: 'wsw'
});
// 定义一个变量接收定时器
let timer = null;
function handleInput(e) {
if(timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log('@@@', e.target.value);
}, 500); // 间隔0.5秒执行打印输出
}
</script>
<template>
<div>
姓名:<input v-model="object.name" @input="handleInput"/>
</div>
</template>
这样控制台输出的结果就会是下面这样:
3.2 结合watch
由于我们要监听是数据object.name
是响应式的,所以也可以结合watch来写防抖函数。
<script setup>
import { ref, watchEffect, reactive } from 'vue'
const object = reactive({ // 响应式对象 object
id: 1,
name: 'wsw'
});
let timer = null;
watch(() => object, () => {
// 如果定时器存在 清除 重新定时
timer && clearTimeout(timer);
// 设置定时器 隔0.5s打印输入框的值
timer = setTimeout(() => {
console.log('@@@', object.name);
}, 500);
}, {deep: true})
</script>
<template>
<div>
姓名:<input v-model="object.name"/> <!-- 双向绑定 -->
</div>
</template>
控制台输出的结果也是一样的:
3.3 结合watchEffect
我们还可以用watchEffect
结合它的onInvalidate
参数来实现(注意watchEffect
不能深度监听对象内的属性,需要根据具体情况使用watch和watchEffect):
<script setup>
import { watchEffect, ref } from 'vue'
const name = ref('wsw');
watchEffect((onInvalidate) => {
const timer = setTimeout(()=>{
console.log('@@@', name.value);
},1000)
// onInvalidate函数
onInvalidate(()=>{
clearTimeout(timer);
})
})
</script>
<template>
<div>
姓名:<input v-model="name" />
</div>
</template>
补充:onInvalidate
是 watchEffect
回调函数里的一个参数,它是一个函数,用于清除 watchEffect
创建的副作用。副作用是指在 watchEffect
中注册的任何代码,它们可能会修改数据、调用其他函数等,但它们不会直接返回一个值。通常,副作用在响应式状态发生变化时被触发执行。
具体来说,onInvalidate
函数会在 watchEffect
注册的响应式数据发生变化并且重新执行回调函数之前被调用。它的作用是清除之前的副作用,以便新的副作用可以被正确地应用。
让chatgpt帮我写个例子快速理解一下onInvalidate
:
<script setup>
import { watchEffect, reactive } from 'vue'
const state = reactive({
count: 0
});
const stop = watchEffect((onInvalidate) => {
console.log('Current count:', state.count);
// 注册副作用
const intervalId = setInterval(() => {
console.log('Interval triggered');
}, 1000);
// 清除之前的副作用
onInvalidate(() => {
clearInterval(intervalId);
console.log('Effect invalidated and cleared');
});
});
// 模拟状态变化
setInterval(() => {
state.count++;
}, 2000);
// 在一定时间后停止 watchEffect
setTimeout(() => {
stop();
console.log('watchEffect stopped');
}, 10000);
</script>
4. 总结
- 防抖通俗来说就是我们希望在用户的操作稳定后才去执行一些处理,避免频繁触发一些响应事件导致性能下降。
- 通过设置定时器
setTimeout
可以来实现防抖函数。 - 在vue3中监听响应式数据还可以结合
watch
和watchEffect
来实现。 - 防抖适用场景:
- 搜索框搜索输入:只需用户最后一次输入完,再发送请求;
- 手机号、邮箱验证输入检测;
- 窗口大小
resize
:只需窗口调整完成后,计算窗口大小。防止重复渲染。