API를 통해서 많은 양의 데이터를 가져와 화면에 렌더링해야 하는 경우 자칫하면 성능 문제를 야기할 수 있음
이 때 성늘을 최적화하기 위한 다양한 방법 중 전통적인 방법으로 페이지네이션
이 있음
페이지네이션은 데이터의 갯수를 미리 알고 있고 데이터의 추가나 삭제가 자주 일어나지 않을 때 유용
이전
, 다음
페이지 링크 제공npx create-react-app modal-playground --template typescript
npm i @emotion/styled @emotion/react react-icons
interface UsePaginationProps {
count: number;
page: number;
onPageChange: (page: number) => void;
disabled?: boolean;
siblingCount?: number;
boundaryCount?: number;
}
const usePagination = ({ count, page, onPageChange, disabled, siblingCount = 1, boundaryCount = 1 }: UsePaginationProps) => {
const range = (start: number, end: number) => {
const length = end - start + 1;
return Array.from({ length }).map((_, index) => index + start);
};
const startPage = 1;
const endPage = count;
const startPages = range(startPage, Math.min(boundaryCount, count));
const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count);
const siblingsStart = Math.max(Math.min(page + 1 - siblingCount, count - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);
const siblingsEnd = Math.min(Math.max(page + 1 + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : endPage - 1);
const itemList = [
"prev",
...startPages,
...(siblingsStart > boundaryCount + 2 ? ["start-ellipsis"] : boundaryCount + 1 < count - boundaryCount ? [boundaryCount + 1] : []),
...range(siblingsStart, siblingsEnd),
...(siblingsEnd < count - boundaryCount - 1 ? ["end-ellipsis"] : count - boundaryCount > boundaryCount ? [count - boundaryCount] : []),
...endPages,
"next",
];
const items = itemList.map((item, index) =>
typeof item === "number"
? {
key: index,
onClick: () => onPageChange(item - 1),
disabled,
selected: item - 1 === page,
item,
}
: {
key: index,
onClick: () => onPageChange(item === "next" ? page + 1 : page - 1),
disabled: disabled || item.indexOf("ellipsis") > -1 || (item === "next" ? page >= count - 1 : page - 1 < 0),
selected: false,
item,
}
);
return {
items,
};
};
export default usePagination;
import React from "react";
import styled from "@emotion/styled/macro";
import { GrFormPrevious, GrFormNext } from "react-icons/gr";
import { AiOutlineEllipsis } from "react-icons/ai";
import usePagination from "../hooks/usePagination";
interface Props {
count: number;
page: number;
onPageChange: (page: number) => void;
disabled?: boolean;
siblingCount?: number;
boundaryCount?: number;
}
const Navigation = styled.nav``;
const Button = styled.button<{ selected?: boolean }>`
color: ${({ selected }) => (selected ? "#fff" : "#000")};
border: 0;
margin: 0;
padding: 8px 12px;
font-size: 16px;
font-weight: normal;
background-color: ${({ selected }) => (selected ? "#36dafa" : "#fff")};
cursor: pointer;
border-radius: 100%;
width: 48px;
height: 48px;
&:hover {
background-color: #ccc;
color: #fff;
}
&:active {
opacity: 0.8;
}
`;
const Item = styled.li``;
const ItemList = styled.ul`
margin: 0;
padding: 0;
display: flex;
list-style: none;
${Item} + ${Item} {
margin-left: 8px;
}
`;
const Pagination: React.FC<Props> = ({ count, page, onPageChange, disabled, siblingCount, boundaryCount }) => {
const getLabel = (item: number | string) => {
if (typeof item === "number") return item;
else if (item.indexOf("ellipsis") > -1) return <AiOutlineEllipsis />;
else if (item.indexOf("prev") > -1) return <GrFormPrevious />;
else if (item.indexOf("next") > -1) return <GrFormNext />;
};
const { items } = usePagination({
count,
page,
onPageChange,
disabled,
siblingCount,
boundaryCount,
});
return (
<Navigation>
<ItemList>
{items.map(({ key, disabled, selected, onClick, item }) => (
<Item key={key}>
<Button disabled={disabled} selected={selected} onClick={onClick}>
{getLabel(item)}
</Button>
</Item>
))}
</ItemList>
</Navigation>
);
};
export default Pagination;
import React, { useState, useEffect } from "react";
import axios from "axios";
import Pagination from "./components/Pagination";
interface Airline {
id: number;
name: string;
country: string;
logo: string;
slogan: string;
head_quaters: string;
website: string;
established: string;
}
interface Passenger {
_id: string;
name: string;
trips: number;
airline: Airline;
__v: number;
}
interface Response {
totalPassengers: number;
totalPages: number;
data: Array<Passenger>;
}
function App() {
const [page, setPage] = useState<number>(0);
const [totalPages, setTotalPages] = useState<number>(0);
const [items, setItems] = useState<Array<Passenger>>([]);
const handlePageChange = (currentPage: number): void => {
setPage(currentPage);
};
useEffect(() => {
const fetch = async () => {
const params = { page, size: 10 };
const {
data: { totalPages, data },
} = await axios.get<Response>("https://api.instantwebtools.net/v1/passenger", { params });
setTotalPages(totalPages);
setItems(data);
};
fetch();
}, [page]);
return (
<div>
<ul>
{items.map((item) => (
<li key={item._id}>{item.name}</li>
))}
</ul>
<Pagination count={totalPages} page={page} onPageChange={handlePageChange} />
</div>
);
}
export default App;
Reference
FastCampus React 강의