Creating a Generic Data Fetching Hook
由于 useGames
和 useGenres
两个 hook 的代码高度相似,因此可以抽象出一个通用的获取数据的 hook。
首先将 useGenres
或者 useGames
的代码复制到 hooks 文件夹下新建的 useData.tsx
:
useData.tsx |
---|
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
43
44
45
46
47 | import { CanceledError } from "axios";
import apiClient from "../services/api-client";
import { useEffect, useState } from "react";
// 删除 Genre 接口,不通用。
interface Genre {
id: number;
name: string;
slug: string;
image_background: string;
}
// 和 Games 的响应结果类似,保留。
interface FetchGenresResponse {
count: number;
results: Genre[];
}
const useGenres = () => {
const [genres, setGenres] = useState<Genre[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
apiClient
.get<FetchGenresResponse>("/genres", { signal: controller.signal })
.then((res) => {
setGenres(res.data.results);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
{
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, []);
return { genres, error, isLoading };
};
export default useGenres;
|
修改,现在需要接受一个端点作为参数,注意通用类型 T 的标注。
useData.tsx |
---|
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 | import { CanceledError } from "axios";
import apiClient from "../services/api-client";
import { useEffect, useState } from "react";
interface FetchResponse<T> {
count: number;
results: T[];
}
const useData = <T>(endpoint: string) => {
const [data, setData] = useState<T[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
apiClient
.get<FetchResponse>(endpoint, { signal: controller.signal })
.then((res) => {
setData(res.data.results);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
{
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, []);
return { data, error, isLoading };
};
export default useData;
|
现在对 useGenres
和 useGames
进行修改,大致就是删去函数体内的代码,转为调用 useData
,传入 API 端点,这也是为什么 useData
中把单个对象的类型标注删去的原因。
useGenres.tsx |
---|
1
2
3
4
5
6
7
8
9
10
11
12 | import useData from "./useData";
interface Genre {
id: number;
name: string;
slug: string;
image_background: string;
}
const useGenres = () => useData<Genre>("/genres");
export default useGenres;
|
由于返回的是 data,因此 GenreList
需要修改下变量名称:
GenreList.tsx |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | import useGenres from "../hooks/useGenres";
const GenreList = () => {
// const { genres } = useGenres();
const { data } = useGenres();
return (
<div>
// {genres.map((genre) => (
{data.map((genre) => (
<ul key={genre.id}>{genre.name}</ul>
))}
</div>
);
};
export default GenreList;
|
同理,useGame
删去了 useData
中的响应类型标注和函数体内的代码,转而使用 useData
传入 API 端点获取响应结果。
useGame.tsx |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | import useData from "./useData";
export interface Platform {
id: number;
name: string;
slug: string;
}
export interface Game {
id: number;
name: string;
metacritic: number;
background_image: string;
parent_platforms: { platform: Platform }[];
}
const useGames = () => useData<Game>("/games");
export default useGames;
|
因为 useData
返回的对象名称是 data
,因此涉及到 GameGrid
调用 useGame
的对象解构。找到 games
,按下 F2,修改名称,这样所有同名的地方都会修改了。
GameGrid |
---|
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 | import { Text, SimpleGrid, Skeleton } from "@chakra-ui/react";
import useGames from "../hooks/useGames";
import GameCard from "./GameCard";
import GameCardSkeleton from "./GameCardSkeleton";
import GameCardContainer from "./GameCardContainer";
const GameGrid = () => {
const { data, error, isLoading } = useGames();
const skeletons = [1, 2, 3, 4, 5, 6];
return (
<>
{error && <Text>{error}</Text>}
<SimpleGrid
columns={{ sm: 1, md: 2, lg: 3, xl: 5 }}
padding="10px"
spacing={10}
>
{isLoading &&
skeletons.map((Skeleton) => (
<GameCardContainer>
<GameCardSkeleton key={Skeleton} />
</GameCardContainer>
))}
{data.map((game) => (
<GameCardContainer>
<GameCard key={game.id} game={game} />
</GameCardContainer>
))}
</SimpleGrid>
</>
);
};
export default GameGrid;
|
2024-07-21 23:49 2024-08-16 23:25