pagination은 웹사이트의 사용성을 크게 향상시킬 수 있는 중요한 기능이다. 사용자가 수많은 데이터 중에서 원하는 정보를 쉽게 찾을 수 있도록 도와주며, 데이터 로딩 시간을 단축시켜 서비스의 퍼포먼스를 개선할 수 있다. 이번 글에서는, 저번 글에서 다룬 모양새만 구현했던 페이지네이션 컴포넌트에 여러 가지 새로운 기능을 추가하여 사용자 경험을 더욱 향상시킨 점에 대해 소개하려고 한다.
"use client";
import Link from "next/link";
import styles from "./Pagination.module.scss";
import React from "react";
interface IPaginationProps {
currentPage: number;
totalContents: number;
paginate: (pageNumber: number) => void;
contentsPerPage: number;
}
function Pagination({
currentPage,
totalContents,
paginate,
contentsPerPage,
}: IPaginationProps): JSX.Element {
const pageNumbers: number[] = [];
for (let i = 1; i <= Math.ceil(totalContents / contentsPerPage); i++) {
pageNumbers.push(i);
}
return (
<>
<nav className={styles.container}>
<ul className={styles.pagination}>
{pageNumbers.map((number) => (
<li
key={number}
className={`${styles.pageList} ${
currentPage === number ? styles.active : ""
}`}
>
<Link
href="/"
onClick={() => paginate(number)}
className={styles.pageLink}
>
{number}
</Link>
</li>
))}
</ul>
</nav>
</>
);
}
export default Pagination;
"use client";
import Link from "next/link";
import styles from "./Pagination.module.scss";
import React, { useState } from "react";
interface IProps {
currentPage: number;
totalContents: number;
paginate: (pageNumber: number) => void;
contentsPerPage: number;
}
function Pagination({ totalContents, contentsPerPage, paginate }: IProps) {
const [currentPage, setCurrentPage] = useState<number>(1);
const pageNumbers: number[] = [];
const maxPageNum = Math.ceil(totalContents / contentsPerPage);
for (let i = 1; i <= maxPageNum; i++) {
pageNumbers.push(i);
}
const updatePageNumbers = (page: number) => {
let startPage = page - 4 > 1 ? page - 4 : 1;
let endPage = startPage + 9 > maxPageNum ? maxPageNum : startPage + 9;
while (endPage - startPage < 9 && startPage > 1) {
startPage--;
}
return pageNumbers.slice(startPage - 1, endPage);
};
const [, setCurrentNumbers] = useState(updatePageNumbers(1));
const visiblePageNumbers = updatePageNumbers(currentPage);
const goToFirstPage = () => {
setCurrentPage(1);
paginate(1);
};
const goToLastPage = () => {
setCurrentPage(maxPageNum);
paginate(maxPageNum);
};
const handleClick = (number: number) => {
setCurrentPage(number);
setCurrentNumbers(updatePageNumbers(number));
paginate(number);
};
return (
<>
<nav className={styles.container}>
<ul className={styles.pagination}>
<li>
<Link
href="#"
onClick={() => goToFirstPage()}
className={`${styles.pageLink} ${
currentPage === 1 ? styles.disabled : null
}`}
>
{"<<"}
</Link>
</li>
<li>
<Link
href="#"
onClick={() => handleClick(currentPage - 1)}
className={`${styles.pageLink} ${
currentPage === 1 ? styles.disabled : null
}`}
>
{"<"}
</Link>
</li>
{visiblePageNumbers.map((number) => {
const pageListClass = `${styles.pageList} ${
currentPage === number ? styles.active : null
}`;
return (
<>
<li key={number} className={pageListClass}>
<Link href="#" onClick={() => handleClick(number)}>
{number}
</Link>
</li>
</>
);
})}
<li>
<Link
href="#"
onClick={() => handleClick(currentPage + 1)}
className={`${styles.pageLink} ${
currentPage === maxPageNum ? styles.disabled : null
}`}
>
{">"}
</Link>
</li>
<li>
<Link
href="#"
onClick={() => goToLastPage()}
className={`${styles.pageLink} ${
currentPage === maxPageNum ? styles.disabled : null
}`}
>
{">>"}
</Link>
</li>
</ul>
</nav>
</>
);
}
export default Pagination;
useState를 사용하여 현재 페이지의 상태를 저장하고 업데이트할 수 있도록 하였다. 이를 통해 어떤 페이지가 현재 선택되어 있는지 사용자에게 명확하게 보여줄 수 있다.
const [currentPage, setCurrentPage] = useState<number>(1);
코드 설명
React의 useState를 사용하여 현재 페이지 번호를 추적한다. 이 상태는 사용자가 페이지 번호를 클릭할 때 업데이트되며, UI에 현재 활성화된 페이지를 반영한다.
전체 콘텐츠 수와 한 페이지에 표시할 콘텐츠 수를 기반으로 총 페이지 수를 계산한다. 이 정보는 사용자가 마지막 페이지에 도달했는지 여부를 판단하는 데 사용된다.
const maxPageNum = Math.ceil(totalContents / contentsPerPage);
코드 설명
전체 콘텐츠 수를 페이지 당 콘텐츠 수로 나누어 총 페이지 수를 계산한다. 이렇게 하면 페이지네이션이 얼마나 많은 페이지를 표시해야 하는지 알 수 있다.
현재 페이지 번호를 기준으로 페이지네이션 컨트롤에 표시할 페이지 번호의 범위를 동적으로 업데이트한다. 이는 사용자가 현재 선택한 페이지 주변의 페이지 번호만 볼 수 있도록 해서, 너무 많은 페이지 번호가 한 번에 표시되는 것을 방지한다.
const updatePageNumbers = (page: number) => {
let startPage = page - 4 > 1 ? page - 4 : 1;
let endPage = startPage + 9 > maxPageNum ? maxPageNum : startPage + 9;
while (endPage - startPage < 9 && startPage > 1) {
startPage--;
}
return pageNumbers.slice(startPage - 1, endPage);
};
const [, setCurrentNumbers] = useState(updatePageNumbers(1));
const visiblePageNumbers = updatePageNumbers(currentPage);
코드 설명
updatePageNumbers 함수는 현재 페이지 번호를 기준으로 페이지네이션 컨트롤에 표시할 페이지 번호의 범위를 동적으로 업데이트한다. 이렇게 하면, 사용자에게 현재 선택한 페이지 주변의 페이지 번호 10개만 보이게 되어 네비게이션이 용이해진다.
사용자가 페이지 번호를 클릭할 때마다, 페이지네이션 컨트롤이 동적으로 업데이트되어 현재 선택된 페이지를 반영한다. 이를 통해 사용자는 항상 어떤 페이지들이 이용 가능한지 명확하게 인지할 수 있다.
const handleClick = (number: number) => {
setCurrentPage(number);
setCurrentNumbers(updatePageNumbers(number));
paginate(number);
};
코드 설명
handleClick 함수는 사용자가 페이지 번호를 클릭할 때 호출되며, 적절한 페이지 번호를 setCurrentPage와 setCurrentNumbers에 설정하여 페이지네이션 UI를 업데이트한다. 이 과정에서 paginate 함수는 새로운 페이지의 콘텐츠를 로드하기 위해 사용된다. (paginate함수는 기존 블로그 참조)
'<' 와 '>' 링크를 통해 사용자가 이전 페이지나 다음 페이지로 손쉽게 이동할 수 있으며, 첫 페이지나 마지막 페이지에서는 해당 링크가 비활성화된다.
<Link
href="#"
onClick={() => handleClick(currentPage - 1)}
className={`${styles.pageLink} ${
currentPage === 1 ? styles.disabled : null
}`}
>
{"<"}
</Link>
// ...
<Link
href="#"
onClick={() => handleClick(currentPage - 1)}
className={`${styles.pageLink} ${
currentPage === maxPageNum ? styles.disabled : null
}`}
>
{">"}
</Link>
코드 설명
Link의 onClick에 handleClick 함수가 연결되어, 페이지를 이동시킬 수 있게 한다. className prop은 동적으로 결정되며, 현재 페이지가 첫 페이지인 경우에, 첫번째 Link는 비활성화 되며, 현재 페이지가 마지막 페이지인 경우에는 두번째 Link가 비활성화 된다.
'<<' 와 '>>' 버튼을 통해 사용자가 첫 페이지나 마지막 페이지로 즉시 이동할 수 있게 함으로써 사용자 경험을 향상시켰다. 마찬가지로 첫 페이지나 마지막 페이지에서는 해당 링크가 비활성화된다.
const goToFirstPage = () => {
setCurrentPage(1);
paginate(1);
};
const goToLastPage = () => {
setCurrentPage(maxPageNum);
paginate(maxPageNum);
};
// ...
<Link
href="#"
onClick={() => goToFirstPage()}
className={`${styles.pageLink} ${
currentPage === 1 ? styles.disabled : null
}`}
>
{"<<"}
</Link>
// ...
<Link
href="#"
onClick={() => goToLastPage ()}
className={`${styles.pageLink} ${
currentPage === maxPageNum ? styles.disabled : null
}`}
>
{">>"}
</Link>
코드 설명
이 함수들은 사용자가 첫 페이지나 마지막 페이지로 즉시 이동할 수 있게 한다. '<<'와 '>>' Link의 onClick에 이 함수들이 연결되어 있어, 사용자가 쉽게 페이지의 시작 또는 끝으로 이동할 수 있다. <>와 마찬가지로 className prop은 동적으로 결정된다.
이전 페이지네이션 구현은 기본적인 요구사항을 충족시켰지만, 몇 가지 핵심 기능에서 제한점을 가지고 있었다. 사용자가 페이지네이션 내에서 더 빠르고 직관적으로 이동할 수 있도록 하는 기능이 필요했다. 이러한 여러가지 아쉬운 점들을 느껴 코드를 개선했다.