반응형
22.2.28 (월) +36days
페이지네이션(Pagination) 이란?
API 를 통해서 많은 양의 데이터를 가져와 화면에 렌더링해야 하는 경우 자칫하면 성능 문제를 야기할 수 있다.
이 때 성능을 최적화하기 위한 다양한 방법 중 전통적인 방법으로 페이지네이션이 있다..
페이지네이션은 데이터의 갯수를 미리 알고 있고 데이터의 추가나 삭제가 자주 일어나지 않을 때 유용하다.
더 나은 경험을 위한 페이지네이션
- 클릭 가능한 요소의 크기를 크게 제공한다.
- 현재 페이지를 표시한다.
- 이전, 다음 페이지 링크를 제공한다.
- 페이지 링크 간 간격을 넓힌다.
Preview
리액트 프로젝트 생성
npx create-react-app pagination-playground --template typescript
패키지 설치
npm i @emotion/styled @emotion/react react-icons
dependencies
페이지네이션(Pagination) 이란?
API 를 통해서 많은 양의 데이터를 가져와 화면에 렌더링해야 하는 경우 자칫하면 성능 문제를 야기할 수 있다.
이 때 성능을 최적화하기 위한 다양한 방법 중 전통적인 방법으로 페이지네이션이 있다..
페이지네이션은 데이터의 갯수를 미리 알고 있고 데이터의 추가나 삭제가 자주 일어나지 않을 때 유용하다.
더 나은 경험을 위한 페이지네이션
- 클릭 가능한 요소의 크기를 크게 제공한다.
- 현재 페이지를 표시한다.
- 이전, 다음 페이지 링크를 제공한다.
- 페이지 링크 간 간격을 넓힌다.
Preview
리액트 프로젝트 생성
npx create-react-app pagination-playground --template typescript
패키지 설치
npm i @emotion/styled @emotion/react react-icons
dependencies
usePagination
interface Props {
count: number;
page: number;
onPageChange: (page: number) => void;
disabled?: boolean;
siblingCount?: number;
boundaryCount?: number;
}
const usePagination = ({
count,
page,
onPageChange,
disabled,
siblingCount = 1,
boundaryCount = 1,
}: Props) => {
// range(2, 5) -> [2, 3, 4, 5]
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;
// 1, 2, ... 6, 7, 8, ... 99, 100
const startPages = range(startPage, Math.min(boundaryCount, count));
const endPages = range(
Math.max(count - boundaryCount + 1, boundaryCount + 1),
count
);
const siblingStart = Math.max(
Math.min(
page + 1 - siblingCount,
count - boundaryCount - siblingCount * 2 - 1
),
boundaryCount + 2
);
const siblingEnd = Math.min(
Math.max(page + 1, siblingCount, boundaryCount + siblingCount * 2 + 2),
endPages.length > 0 ? endPages[0] - 2 : endPage - 1
);
const itemList = [
'prev',
...startPages,
...(siblingStart > boundaryCount + 2
? ['start-ellipsis']
: boundaryCount + 1 < count - boundaryCount
? [boundaryCount + 1]
: []),
...range(siblingStart, siblingEnd),
...(siblingEnd < 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),
selected: false,
item,
}
);
return { items };
};
export default usePagination;
Pagination.tsx
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 ? '#3d6afe' : '#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;
}
`;
export default function Pagination({
count,
page,
onPageChange,
disabled,
siblingCount,
boundaryCount,
}: Props) {
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>
);
}
App.tsx
import { useEffect, useState } 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(0);
const [totalPages, setTotalPages] = useState(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 className="App">
<ul>
{items.map((item) => (
<li key={item._id}>{item.name}</li>
))}
</ul>
<Pagination
count={totalPages}
page={page}
onPageChange={handlePageChange}
/>
</div>
);
}
export default App;
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다
반응형
댓글