uchill/front_material/hooks/useOptimizedFetch.ts

121 lines
3.5 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 { useState, useEffect, useCallback, useRef } from 'react';
import { cache } from '@/lib/cache';
import apiClient from '@/lib/api-client';
interface UseOptimizedFetchOptions<T> {
url: string;
cacheKey?: string;
/** Не используется пока кэш отключён глобально. Оставлен для последующего включения по месту. */
cacheTTL?: number;
enabled?: boolean;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
}
interface UseOptimizedFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
clearCache: () => void;
mutate: (updater: T | ((prev: T | null) => T | null)) => void;
}
export function useOptimizedFetch<T = any>({
url,
cacheKey,
cacheTTL,
enabled = true,
onSuccess,
onError,
}: UseOptimizedFetchOptions<T>): UseOptimizedFetchResult<T> {
const key = cacheKey || url;
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const fetchData = useCallback(async () => {
// Отменяем предыдущий запрос
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
try {
setLoading(true);
setError(null);
// Кэш отключён везде до анализа на Production (см. lib/cache.ts, api-client.ts)
const response = await apiClient.get<T>(url, {
signal: abortControllerRef.current.signal,
cache: false,
});
const newData = response.data;
setData(newData);
onSuccess?.(newData);
} catch (err: any) {
// Не показываем ошибку при отмене запроса (навигация, размонтирование, refetch)
const isCanceled =
err?.name === 'AbortError' ||
err?.name === 'CanceledError' ||
err?.code === 'ERR_CANCELED' ||
err?.message === 'canceled';
if (isCanceled) {
return;
}
const error = err instanceof Error ? err : new Error(err?.message || 'Unknown error');
setError(error);
onError?.(error);
} finally {
if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) {
setLoading(false);
}
}
}, [url, onSuccess, onError]);
useEffect(() => {
if (!enabled) {
setLoading(false);
return;
}
fetchData();
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [enabled, fetchData]);
const clearCache = useCallback(() => {
cache.delete(key);
setData(null);
}, [key]);
// Локальное обновление данных без перезагрузки (кэш отключён)
const mutate = useCallback((updater: T | ((prev: T | null) => T | null)) => {
setData((prev) => {
return typeof updater === 'function'
? (updater as (prev: T | null) => T | null)(prev)
: updater;
});
}, []);
return {
data,
loading,
error,
refetch: fetchData,
clearCache,
mutate,
};
}