2026-07-14 10:31:17 +08:00
|
|
|
// src/hooks/useDebounce.ts
|
2026-07-27 09:51:30 +08:00
|
|
|
import { onUnmounted, Ref } from 'vue';
|
2026-07-14 10:31:17 +08:00
|
|
|
import { useState } from './useState';
|
2026-07-27 09:51:30 +08:00
|
|
|
import { useEffect } from './useEffect';
|
2026-07-14 10:31:17 +08:00
|
|
|
|
|
|
|
|
export interface UseDebounceOptions {
|
|
|
|
|
delay?: number;
|
|
|
|
|
immediate?: boolean;
|
|
|
|
|
maxWait?: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-24 15:57:20 +08:00
|
|
|
export function useDebounce<T>(source: Ref<T>, options: number | UseDebounceOptions = {}) {
|
2026-07-14 10:31:17 +08:00
|
|
|
const config = typeof options === 'number' ? { delay: options } : options;
|
|
|
|
|
const { delay = 300, immediate = false, maxWait } = config;
|
|
|
|
|
|
2026-07-24 15:57:20 +08:00
|
|
|
const [debouncedValue, setDebouncedValue] = useState<T>(source.value as T);
|
2026-07-14 10:31:17 +08:00
|
|
|
const [isPending, setIsPending] = useState(false);
|
|
|
|
|
|
|
|
|
|
let timer: any = null;
|
|
|
|
|
let maxWaitTimer: any = null;
|
|
|
|
|
let isFirstCall = true; // 标记是否为首次调用,用于 immediate 模式
|
|
|
|
|
|
|
|
|
|
// 清理
|
|
|
|
|
const clearTimers = () => {
|
|
|
|
|
if (timer) {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
timer = null;
|
|
|
|
|
}
|
|
|
|
|
if (maxWaitTimer) {
|
|
|
|
|
clearTimeout(maxWaitTimer);
|
|
|
|
|
maxWaitTimer = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 更新
|
|
|
|
|
const updateValue = () => {
|
|
|
|
|
setDebouncedValue(source.value);
|
|
|
|
|
setIsPending(false);
|
|
|
|
|
clearTimers();
|
|
|
|
|
// 安静期结束后重置 isFirstCall,使下次触发能立即执行(immediate 模式)
|
|
|
|
|
isFirstCall = true;
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-27 09:51:30 +08:00
|
|
|
useEffect(() => {
|
2026-07-14 10:31:17 +08:00
|
|
|
setIsPending(true);
|
|
|
|
|
|
|
|
|
|
// 处理 maxWait
|
|
|
|
|
if (maxWait && !maxWaitTimer) {
|
|
|
|
|
maxWaitTimer = setTimeout(() => {
|
|
|
|
|
updateValue();
|
|
|
|
|
}, maxWait);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理 delay
|
|
|
|
|
if (timer) clearTimeout(timer);
|
|
|
|
|
|
|
|
|
|
if (immediate && isFirstCall) {
|
|
|
|
|
isFirstCall = false;
|
|
|
|
|
updateValue();
|
|
|
|
|
} else {
|
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
|
updateValue();
|
|
|
|
|
}, delay);
|
|
|
|
|
}
|
2026-07-27 09:51:30 +08:00
|
|
|
|
|
|
|
|
return clearTimers;
|
|
|
|
|
}, [source]);
|
2026-07-14 10:31:17 +08:00
|
|
|
|
|
|
|
|
// 卸载
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
clearTimers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
debouncedValue,
|
|
|
|
|
isPending,
|
|
|
|
|
refresh: updateValue,
|
|
|
|
|
cancel: clearTimers,
|
|
|
|
|
};
|
|
|
|
|
}
|