Loading Skeleton
首先设置加载状态,分别在获取数据成功和出错时设置加载结束。
useGames.ts |
---|
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
48
49
50
51
52
53 | import { CanceledError } from "axios";
import apiClient from "../services/api-client";
import { useEffect, useState } from "react";
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 }[];
}
interface FetchGamesResponse {
count: number;
results: Game[];
}
const useGames = () => {
const [games, setGames] = useState<Game[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
apiClient
.get<FetchGamesResponse>("/games", { signal: controller.signal })
.then((res) => {
setGames(res.data.results);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
{
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, []);
return { games, error, isLoading };
};
export default useGames;
|
然后新建一个骨架屏的组件,宽度圆角等样式需和 GameCard
保持一致。
这里需要给出固定的宽度是因为我们没有给边栏设定固定的宽度,导致在获取到图片之前与获取到图片之后的卡片前后宽度不一样,所以骨架屏和最终卡片不一致,导致需要给骨架屏和卡片固定的宽度,见 [[14. Displaying the Genres]] 。
GameCardSkeleton.tsx |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13 | import { Card, CardBody, Skeleton, SkeletonText } from "@chakra-ui/react
const GameCardSkeleton = () => {
return (
<Card width="300px" borderRadius={10} overflow="hidden">
<Skeleton height="200px" />
<CardBody>
<SkeletonText />
</CardBody>
</Card>
);
};
export default GameCardSkeleton;
|
GameCard.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 | import { Card, Image, CardBody, Heading, HStack } from "@chakra-ui/react";
import { Game } from "../hooks/useGames";
import PlatformIconList from "./PlatformIconList";
import CriticScore from "./CriticScore";
import getCroppedImageUrl from "../services/image-url";
interface Props {
game: Game;
}
const GameCard = ({ game }: Props) => {
return (
<Card width="300px" borderRadius="10px" overflow="hidden">
<Image src={getCroppedImageUrl(game.background_image)} />
<CardBody>
<Heading fontSize="2xl">{game.name}</Heading>
<HStack justifyContent={"space-between"}>
<PlatformIconList
platforms={game.parent_platforms.map(({ platform }) => platform)}
/>
<CriticScore score={game.metacritic} />
</HStack>
</CardBody>
</Card>
);
};
export default GameCard;
|
在负责卡片布局的组件中,根据加载状态决定是否要显示骨架屏:
GameGrid.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 | import { Text, SimpleGrid, Skeleton } from "@chakra-ui/react";
import useGames from "../hooks/useGames";
import GameCard from "./GameCard";
import GameCardSkeleton from "./GameCardSkeleton";
const GameGrid = () => {
const { games, 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) => <GameCardSkeleton key={Skeleton} />)}
{games.map((game) => (
<GameCard key={game.id} game={game} />
))}
</SimpleGrid>
</>
);
};
export default GameGrid;
|
2024-07-20 17:41 2024-07-22 01:18