1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // hooks/useFetch.ts
import { useState, useEffect, useCallback, useRef } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
export function useFetch<T>(
fetchFn: () => Promise<T>,
deps: React.DependencyList = []
) {
const [state, setState] = useState<FetchState<T>>({
data: null, loading: true, error: null,
});
// 用 ref 标记是否已卸载,防止竞态条件下的 setState 报错
const mountedRef = useRef(true);
useEffect(() => () => { mountedRef.current = false; }, []);
const execute = useCallback(async () => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const data = await fetchFn();
if (mountedRef.current) setState({ data, loading: false, error: null });
} catch (err) {
if (mountedRef.current)
setState({ data: null, loading: false, error: err as Error });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
useEffect(() => { execute(); }, [execute]);
return { ...state, refetch: execute };
}
// 用法
const { data: posts, loading, error, refetch } = useFetch(
() => postApi.getAll(), []
);
|