Files
cunkebao_v3/nkebao/src/hooks/useThrottledRequest.ts
2025-07-05 21:40:14 +08:00

265 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useCallback, useRef, useState, useEffect } from 'react';
import { requestDeduplicator, requestCancelManager } from '../api/utils';
// 节流请求Hook
export const useThrottledRequest = <T extends (...args: any[]) => any>(
requestFn: T,
delay: number = 1000
) => {
const lastCallRef = useRef(0);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const throttledRequest = useCallback(
((...args: any[]) => {
const now = Date.now();
if (now - lastCallRef.current < delay) {
// 如果在节流时间内,取消之前的定时器并设置新的
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
lastCallRef.current = now;
requestFn(...args);
}, delay - (now - lastCallRef.current));
} else {
// 如果超过节流时间,直接执行
lastCallRef.current = now;
requestFn(...args);
}
}) as T,
[requestFn, delay]
);
return throttledRequest;
};
// 防抖请求Hook
export const useDebouncedRequest = <T extends (...args: any[]) => any>(
requestFn: T,
delay: number = 300
) => {
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const debouncedRequest = useCallback(
((...args: any[]) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
requestFn(...args);
}, delay);
}) as T,
[requestFn, delay]
);
return debouncedRequest;
};
// 带加载状态的请求Hook
export const useRequestWithLoading = <T extends (...args: any[]) => Promise<any>>(
requestFn: T
) => {
const [loading, setLoading] = useState(false);
const requestWithLoading = useCallback(
(async (...args: any[]) => {
if (loading) {
console.log('请求正在进行中,跳过重复请求');
return;
}
setLoading(true);
try {
const result = await requestFn(...args);
return result;
} finally {
setLoading(false);
}
}) as T,
[requestFn, loading]
);
return { requestWithLoading, loading };
};
// 组合Hook节流 + 加载状态
export const useThrottledRequestWithLoading = <T extends (...args: any[]) => Promise<any>>(
requestFn: T,
delay: number = 1000
) => {
const { requestWithLoading, loading } = useRequestWithLoading(requestFn);
const throttledRequest = useThrottledRequest(requestWithLoading, delay);
return { throttledRequest, loading };
};
// 带错误处理的请求Hook
export const useRequestWithError = <T extends (...args: any[]) => Promise<any>>(
requestFn: T
) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const requestWithError = useCallback(
(async (...args: any[]) => {
setError(null);
setLoading(true);
try {
const result = await requestFn(...args);
return result;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '请求失败';
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
}) as T,
[requestFn]
);
return { requestWithError, loading, error, clearError: () => setError(null) };
};
// 带重试的请求Hook
export const useRequestWithRetry = <T extends (...args: any[]) => Promise<any>>(
requestFn: T,
maxRetries: number = 3,
retryDelay: number = 1000
) => {
const [loading, setLoading] = useState(false);
const [retryCount, setRetryCount] = useState(0);
const requestWithRetry = useCallback(
(async (...args: any[]) => {
setLoading(true);
setRetryCount(0);
let lastError: any;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
setRetryCount(attempt);
const result = await requestFn(...args);
return result;
} catch (error) {
lastError = error;
if (attempt === maxRetries) {
throw error;
}
// 等待后重试
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
throw lastError;
}) as T,
[requestFn, maxRetries, retryDelay]
);
return { requestWithRetry, loading, retryCount };
};
// 可取消的请求Hook
export const useCancellableRequest = <T extends (...args: any[]) => Promise<any>>(
requestFn: T
) => {
const [loading, setLoading] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
const cancellableRequest = useCallback(
(async (...args: any[]) => {
// 取消之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 创建新的AbortController
abortControllerRef.current = new AbortController();
setLoading(true);
try {
const result = await requestFn(...args);
return result;
} finally {
setLoading(false);
abortControllerRef.current = null;
}
}) as T,
[requestFn]
);
const cancelRequest = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
setLoading(false);
abortControllerRef.current = null;
}
}, []);
// 组件卸载时取消请求
useEffect(() => {
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
return { cancellableRequest, loading, cancelRequest };
};
// 组合Hook节流 + 加载状态 + 错误处理
export const useThrottledRequestWithError = <T extends (...args: any[]) => Promise<any>>(
requestFn: T,
delay: number = 1000
) => {
const { requestWithError, loading, error, clearError } = useRequestWithError(requestFn);
const throttledRequest = useThrottledRequest(requestWithError, delay);
return { throttledRequest, loading, error, clearError };
};
// 组合Hook防抖 + 加载状态 + 错误处理
export const useDebouncedRequestWithError = <T extends (...args: any[]) => Promise<any>>(
requestFn: T,
delay: number = 300
) => {
const { requestWithError, loading, error, clearError } = useRequestWithError(requestFn);
const debouncedRequest = useDebouncedRequest(requestWithError, delay);
return { debouncedRequest, loading, error, clearError };
};
// 请求状态监控Hook
export const useRequestMonitor = () => {
const [pendingCount, setPendingCount] = useState(0);
useEffect(() => {
const updatePendingCount = () => {
setPendingCount(requestDeduplicator.getPendingCount());
};
// 初始更新
updatePendingCount();
// 定期检查待处理请求数量
const interval = setInterval(updatePendingCount, 100);
return () => clearInterval(interval);
}, []);
const cancelAllRequests = useCallback(() => {
requestCancelManager.cancelAllRequests();
requestDeduplicator.clear();
}, []);
return { pendingCount, cancelAllRequests };
};