컴포넌트 개요
- API를 통해 전체 데이터 개수를 받아오고, 페이지 수를 계산하여 현재 페이지 기반의 페이지네이션 버튼 렌더링
- 최대 5개의 페이지 버튼이 한 번에 보이도록 구성하기
1. 필요한 Props 타입 정의
- pageSize : 한 페이지에 보여줄 항목 개수
- currentPage : 현재 선택된 페이지 번호
- onPageChange : 페이지 번호 변경 시 실행할 콜백 함수
type PaginationProps = {
pageSize: number;
currentPage: number;
onPageChange: (page: number) => void;
};
2. 전체 페이지 수 계산
- 컴포넌트 마운트 시 또는 pageSize가 바뀔 때마다 전체 데이터 개수 가져와서 totalPage 계산하기
useEffect(() => {
const fetchData = async () => {
const { count } = await getSubjects();
setTotalPage(Math.ceil(count / pageSize));
};
fetchData();
}, [pageSize]);
3. 현재 페이지 블록 계산
- 한 번에 보여줄 최대 페이지 수 : 5개
- 현재 페이지가 속한 블록의 시작/끝 번호를 계산해서 visiblePages 배열 구성
const maxVisible = 5;
const currentBlock = Math.floor((currentPage - 1) / maxVisible);
const startPage = currentBlock * maxVisible + 1;
const endPage = Math.min(startPage + maxVisible - 1, totalPage);
왜 currentBlock을 사용하는가?
- 예를 들어, 페이지가 100개라면 사용자에게는 5개씩 묶인 페이지 버튼이 보이게 해야함
- [1,2,3,4,5] -> currentBlock : 0
- [6,7,8,9,10] -> currentBlock : 1
- [11,12,13,14,15] -> currentBlock : 2
- 따라서 현재 페이지가 어느 블록에 속하는지를 계산해야함
- 이 값을 기준으로 startPage, endPage를 산출하여 보여줄 페이지 버튼만 추림
4. 페이지 버튼 리스트 생성
- 위에서 계산된 startPage ~ endPage 사이의 번호를 visiblePages 배열에 저장
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
5. 이전/다음 페이지 이동 함수
const gotoPrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const gotoNext = () => {
if (currentPage < totalPage) {
onPageChange(currentPage + 1);
}
};
6. 전체 코드
type PaginationProps = {
pageSize: number;
currentPage: number;
onPageChage: (page: number) => void;
};
function Pagination({ pageSize, currentPage, onPageChage }: PaginationProps) {
const [totalPage, setTotalPage] = useState(0);
useEffect(() => {
const fetchData = async () => {
const { count } = await getSubjects();
setTotalPage(Math.ceil(count / pageSize));
};
fetchData();
}, [pageSize]);
const maxVisible = 5;
const currentBlock = Math.floor((currentPage - 1) / maxVisible);
const startPage = currentBlock * maxVisible + 1;
const endPage = Math.min(startPage + maxVisible - 1, totalPage);
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
const gotoPrev = () => {
if (currentPage > 1) {
onPageChage(currentPage - 1);
}
};
const gotoNext = () => {
if (currentPage < totalPage) {
onPageChage(currentPage + 1);
}
};
return (
<div className={styles.pagination}>
<button
className={styles["arrow-button"]}
onClick={gotoPrev}
disabled={currentPage === 1}
>
<Image src={arrowLeft} alt="이전 페이지" />
</button>
<div className={styles["pagination-button-wrapper"]}>
{visiblePages.map((number) => (
<button
key={number}
className={`${styles["pagination-button"]} ${
currentPage === number ? styles.active : ""
}`}
onClick={() => onPageChage(number)}
>
{number}
</button>
))}
</div>
<button
className={styles["arrow-button"]}
onClick={gotoNext}
disabled={currentPage === totalPage}
>
<Image src={arrowRight} alt="다음 페이지" />
</button>
</div>
);
}
7. 최적화(React.memo 사용)
export default React.memo(Pagination);