防抖(Debounce)概念
防抖是一种优化技术,用于限制函数的执行频率。当一个函数被频繁调用时(比如用户输入、窗口大小调整等),防抖可以确保函数只在最后一次调用后的一段时间内执行一次,避免不必要的重复执行。
生活中的例子
想象你在电梯门前,当有人要进入电梯时,电梯门会延迟关闭,如果在这期间又有人要进来,电梯门会重新开始延迟计时。只有当一段时间内没有人再进来时,电梯门才会真正关闭。这就是防抖的原理。
代码详解
import { useCallback, useRef } from 'react';
/**
* 防抖Hook - 用于延迟执行函数调用,避免函数被频繁触发
*
* 防抖原理:当函数被触发时,设置一个延迟定时器,如果在延迟时间内函数再次被触发,
* 则清除之前的定时器并重新设置,直到延迟时间到达后才真正执行函数
*
* @param {Function} callback - 需要防抖的回调函数
* @param {number} delay - 防抖延迟时间(毫秒)
* @returns {Function} - 防抖后的函数
*
* @example
* // 创建一个防抖搜索函数,延迟500ms执行
* const debouncedSearch = useDebounce(async (query) => {
* const result = await searchAPI(query);
* return result;
* }, 500);
*
* // 使用防抖函数
* const handleSearch = async (value) => {
* try {
* // 只有在用户停止输入500ms后才会真正调用searchAPI
* const result = await debouncedSearch(value);
* console.log(result);
* } catch (error) {
* console.error(error);
* }
* };
*
* // 应用场景:
* // 1. 搜索框输入实时搜索(避免每次输入都发送请求)
* // 2. 窗口大小调整事件处理
* // 3. 按钮点击防止重复提交
* // 4. 表单验证(避免每次输入都进行验证)
*/
export const useDebounce = (callback, delay) => {
// 使用useRef保存定时器引用,确保在多次渲染间保持同一引用
// 这样可以在每次调用时清除之前的定时器
const timeoutRef = useRef(null);
// 使用useCallback缓存返回的函数,避免不必要的重新创建
// 依赖项为callback和delay,当它们变化时才重新创建函数
return useCallback((...args) => {
// 清除之前的定时器,防止函数执行
// 这是防抖的关键步骤,确保只有最后一次调用会生效
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// 返回Promise以支持异步操作
// 这样调用方可以使用async/await处理异步结果
return new Promise((resolve, reject) => {
// 设置新的定时器,在延迟时间到达后执行回调函数
timeoutRef.current = setTimeout(async () => {
try {
// 执行传入的回调函数,并传入所有参数
// 使用async/await支持异步回调函数
const result = await callback(...args);
// 成功执行后,通过resolve返回结果
resolve(result);
} catch (error) {
// 如果回调函数执行出错,通过reject抛出错误
reject(error);
}
}, delay);
});
}, [callback, delay]);
};
export default useDebounce;
1. Hook 签名和文档
export const useDebounce = (callback, delay) => {
这个 Hook 接收两个参数:
- callback : 需要防抖的函数
- delay : 延迟时间(毫秒)
2. 定时器引用
const timeoutRef = useRef(null);
使用 useRef 保存定时器引用,确保在多次渲染之间保持对同一个定时器的引用。这是防抖实现的关键,因为我们需要能够清除之前的定时器。
3. 缓存返回函数
return useCallback((...args) => {
// 函数实现
}, [callback, delay]);
使用 useCallback 缓存返回的函数,避免在每次重新渲染时创建新函数。只有当 callback 或 delay 变化时才会重新创建。
4. 防抖核心逻辑
// 清除之前的定时器
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// 设置新的定时器
timeoutRef.current = setTimeout(async () => {
const result = await callback(...args);
resolve(result);
}, delay);
这部分是防抖的核心实现:
- 每次函数被调用时,首先清除之前的定时器
- 然后设置一个新的定时器,在延迟时间后执行回调函数
- 如果在延迟时间内函数再次被调用,会重新执行第1步和第2步
5. Promise 支持
return new Promise((resolve, reject) => {
timeoutRef.current = setTimeout(async () => {
try {
const result = await callback(...args);
resolve(result);
} catch (error) {
reject(error);
}
}, delay);
});
通过返回 Promise,这个 Hook 支持异步回调函数,调用方可以使用 async/await 处理异步结果。
实际使用场景
1. 搜索框输入防抖
const debouncedSearch = useDebounce(async (query) => {
const result = await searchAPI(query);
return result;
}, 500);
const handleSearch = async (value) => {
try {
// 用户快速输入时,只会发送最后一次请求
const result = await debouncedSearch(value);
setSearchResults(result);
} catch (error) {
console.error('搜索失败:', error);
}
};
2. 窗口大小调整
const debouncedResize = useDebounce((size) => {
// 处理窗口大小调整逻辑
updateLayout(size);
}, 300);
useEffect(() => {
const handleResize = () => {
debouncedResize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [debouncedResize]);
工作流程图解
用户输入: a ------------------ ab ------------------- abc ------>
↑ ↑ ↑
调用1 调用2 调用3
| | |
清除定时器 清除定时器 清除定时器
| | |
设置定时器T1 设置定时器T2 设置定时器T3
| | |
T1被清除 T2被清除 T3执行回调
| | |
+---------------------+----------------------↓
执行一次回调函数
优势
- 性能优化:减少不必要的函数调用,降低系统负载
- 用户体验:避免频繁的网络请求或界面更新
- 灵活性:支持同步和异步回调函数
- 易用性:封装成 Hook,使用简单方便
这个防抖 Hook 是一个非常实用的工具,可以有效提升应用的性能和用户体验。