Files
2026-04-23 18:58:13 +08:00

61 lines
1.5 KiB
TypeScript

'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
type UseApiState<T> = {
data: T | null;
loading: boolean;
error: string | null;
};
/**
* 通用数据获取 hook,封装 loading / error / data 状态 + AbortController 取消。
*
* @param fetcher 返回 Promise 的数据获取函数,接收 AbortSignal
* @param deps 依赖数组,变化时重新请求
*/
export function useApi<T>(
fetcher: (signal?: AbortSignal) => Promise<T>,
deps: unknown[] = [],
): UseApiState<T> & { refetch: () => void } {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: true,
error: null,
});
const abortRef = useRef<AbortController | null>(null);
const fetcherRef = useRef(fetcher);
fetcherRef.current = fetcher;
const load = useCallback(() => {
abortRef.current?.abort();
const ac = new AbortController();
abortRef.current = ac;
setState((s) => ({ ...s, loading: true, error: null }));
fetcherRef
.current(ac.signal)
.then((data) => {
if (!ac.signal.aborted) {
setState({ data, loading: false, error: null });
}
})
.catch((e: unknown) => {
if (!ac.signal.aborted) {
setState({ data: null, loading: false, error: e instanceof Error ? e.message : String(e) });
}
});
}, deps);
useEffect(() => {
load();
return () => {
abortRef.current?.abort();
};
}, [load]);
return { ...state, refetch: load };
}