前言 📝
在前端开发中,防抖(debounce)是一种常见的技术,用于限制函数执行的频率。特别是在处理用户输入、窗口大小调整等频繁触发的事件时,防抖可以显著提高性能。本文将探讨在Vue3中实现防抖的几种方法,并分析它们的优缺点。
1. 什么是防抖?
防抖是一种技术,它确保一个函数在一定时间内只执行一次,即使它被多次调用。如果在等待时间内再次调用该函数,计时器会重置。
典型应用场景:
- 搜索框输入建议
- 窗口大小调整事件
- 滚动事件处理
2. Vue中的防抖实现方案
2.1 使用工具函数实现防抖
export default function debounce<T extends (...args: any[]) => any>(
fn: T,
delay = 300
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null;
return function(...args: Parameters<T>) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}
使用:
<script setup lang="ts">
import { ref } from "vue";
import debounce from './debounce.ts'
const message = ref('');
const handleChange = (e) => {
console.log('执行')
message.vlaue = e.target.value
}
const debounceFn = debounce(handleChange, 3000)
</script>
<template>
<input @input="debounceFn" />
<p style="color: #fff">{{message}}</p>
</template>
优点:
- 通用性强,可以在任何地方使用
- 不依赖Vue特性,纯JavaScript实现
缺点:
- 需要手动处理事件对象
- 在模板中使用时不够直观
2.2 响应式Ref防抖
import { customRef, onUnmounted } from "vue";
export default function useDebounceRef<T>(value: T, delay = 200) {
let timer: ReturnType<typeof setTimeout> | null = null;
// 组件卸载时自动清除
onUnmounted(() => {
if (timer) clearTimeout(timer)
})
return customRef((track, trigger) => ({
get() {
track();
return value;
},
set(newValue: T) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
}));
}
使用:
<script setup lang="ts">
import { ref } from "vue";
import useDebounceRef from './useDebounceRef.ts'
const message = useDebounceRef('', 3000);
</script>
<template>
<input v-model="message" />
<p style="color: #fff">{{message}}</p>
</template>
优点:
- 无缝支持 v-model:可以直接在模板中使用
v-model
绑定,开发者体验极佳 - 自动依赖追踪:通过
track()
和trigger()
方法完美融入 Vue 的响应式系统 - 类型安全:完整的 TypeScript 支持,保留了类型推断
- 简洁直观的 API 设计
缺点:
- 灵活性受限
- 调试复杂性提升
customRef
谨慎使用 当使用 customRef 时,我们应该谨慎对待其 getter 的返回值,尤其是在每次运行 getter 时都生成新对象数据类型的情况下。当这样的 customRef 作为 prop 传递时,将影响父组件和子组件之间的关系。
父组件的渲染函数可能会被其他的响应式状态变化触发。在重新渲染过程中,我们会重新评估 customRef 的值,并返回一个新的对象数据类型作为子组件的 prop。这个 prop 会与其上一个值进行比较,由于两者不同,子组件中 customRef 的响应式依赖将被触发。与此同时,因为没有调用 customRef 的 setter,父组件中的响应式依赖不会运行。