Skip to content

Commit 1e9e886

Browse files
committed
feat: add useDebounce hook
1 parent 5de7eae commit 1e9e886

4 files changed

Lines changed: 116 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
# Changelog
44

5+
## 1.4.16
6+
7+
2025-9-18
8+
9+
### Features
10+
11+
#### `useDebounce`
12+
13+
- ✨ add `useDebounce` hook
14+
515
## 1.4.15
616

717
2025-9-16

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tiny-codes/react-easy",
3-
"version": "1.4.15",
3+
"version": "1.4.16",
44
"description": "Simplify React and AntDesign development with practical components and hooks",
55
"keywords": [
66
"react",

src/hooks/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
export { default as useAudioPlayer } from './useAudioPlayer';
2+
export { default as useDebounce } from './useDebounce';
23
export { default as useRefValue } from './useRefValue';
34
export { default as useRefFunction } from './useRefFunction';
45
export * from './useSSE';
56
export { default as useSSE } from './useSSE';
67
export * from './useStompSocket';
78
export { default as useStompSocket } from './useStompSocket';
9+
export * from './useUserMedia';
10+
export { default as useUserMedia } from './useUserMedia';
811
export type { ValidatorRuleMap } from './useValidators';
912
export { default as useValidators } from './useValidators';
1013
export { default as useValidator } from './useValidator';
1114
export type { Validator, RuleRegExpFlags, ValidatorRule, BuilderOptions } from './useValidatorBuilder';
1215
export { default as useValidatorBuilder } from './useValidatorBuilder';
13-
export * from './useUserMedia';
14-
export { default as useUserMedia } from './useUserMedia';

src/hooks/useDebounce.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { useCallback, useEffect, useRef } from 'react';
2+
3+
export interface UseDebounceOptions {
4+
/**
5+
* - **EN** Whether to execute at the start of the wait period. Default is `false`.
6+
* - **CN** 是否在等待周期开始时执行,默认值为 `false`
7+
*/
8+
leading?: boolean;
9+
/**
10+
* - **EN** Regular debounce interval in milliseconds. Default is `0`, meaning no debounce.
11+
* - **CN** 常规防抖间隔 (ms),默认值为 `0`, 表示不进行防抖
12+
*/
13+
wait?: number;
14+
/**
15+
* - **EN** Maximum wait time in milliseconds. Default is `0`, meaning no maximum wait.
16+
* - **CN** 最大等待时间 (ms),默认值为 `0`, 表示不限制最大等待时间
17+
*/
18+
maxWait?: number;
19+
}
20+
/**
21+
* - **EN** Debounce Hook with dual trigger mechanisms:
22+
*
23+
* 1. Traditional debounce: Executes after a specified interval without new calls.
24+
* 2. Max wait: Forces execution after exceeding a specified maximum wait time.
25+
* - **CN** 防抖 Hook:具有两种触发机制:
26+
*
27+
* 1. 传统防抖:等待指定时间内无新调用后执行
28+
* 2. 最大等待:超过指定最大等待时间后强制执行
29+
*
30+
* @param fn The function to debounce | 需要防抖的函数
31+
* @param deps Dependency array, re-creates the debounced function when dependencies change |
32+
* 依赖项数组,当依赖变化时重新创建防抖函数
33+
* @param options Configuration options | 配置选项
34+
*
35+
* @returns The debounced function | 防抖处理后的函数
36+
*/
37+
function useDebounce<T extends (...args: any[]) => unknown>(
38+
fn: T,
39+
deps: React.DependencyList,
40+
options: UseDebounceOptions = {}
41+
): (...args: Parameters<T>) => void {
42+
const { wait = 0, maxWait = 0, leading = false } = options;
43+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
44+
const fnRef = useRef<T>(fn);
45+
const lastExecutedTimeRef = useRef<number>(0); // The last execution timestamp
46+
const lastArgsRef = useRef<unknown[]>([]); // The last call arguments
47+
48+
// Update the latest function reference
49+
useEffect(() => {
50+
fnRef.current = fn;
51+
}, [fn]);
52+
53+
// Cleanup on unmount
54+
useEffect(() => {
55+
return () => {
56+
if (timeoutRef.current) {
57+
clearTimeout(timeoutRef.current);
58+
timeoutRef.current = null;
59+
}
60+
};
61+
}, []);
62+
63+
// The actual execution function
64+
const executeFunction = useCallback(() => {
65+
timeoutRef.current = null;
66+
lastExecutedTimeRef.current = Date.now();
67+
fnRef.current(...lastArgsRef.current);
68+
}, []);
69+
70+
// The debounced function
71+
return useCallback(
72+
(...args: Parameters<T>) => {
73+
const now = Date.now();
74+
lastArgsRef.current = args;
75+
76+
// If leading is true and it's the first call, execute immediately
77+
if (leading && timeoutRef.current === null && now - lastExecutedTimeRef.current >= wait) {
78+
executeFunction();
79+
return;
80+
}
81+
82+
// 1. Clear the existing timer
83+
if (timeoutRef.current !== null) {
84+
clearTimeout(timeoutRef.current);
85+
timeoutRef.current = null;
86+
}
87+
88+
// 2. Check if the maximum wait time has been exceeded, if so, execute immediately
89+
if (maxWait > 0 && now - lastExecutedTimeRef.current >= maxWait) {
90+
executeFunction();
91+
return;
92+
}
93+
94+
// 3. Set a new debounce timer
95+
timeoutRef.current = setTimeout(executeFunction, wait);
96+
},
97+
// eslint-disable-next-line @tiny-codes/react-hooks/exhaustive-deps
98+
[wait, maxWait, executeFunction, leading, ...deps]
99+
);
100+
}
101+
102+
export default useDebounce;

0 commit comments

Comments
 (0)