이번 글에서는 외부 라이브러리 없이, 순수 React와 Recoil을 사용하여 간단한 페이지네이션 컴포넌트를 구현한 방법을 공유합니다.
페이지네이션은 기본적으로 페이지 번호를 표시하고, 페이지를 이동할 수 있는 버튼(이전, 다음)을 제공합니다. 페이지네이션을 구현하기 위해 필요한 주요 요소는 다음과 같습니다.
현재 페이지 번호(currentPageNum)
시작 페이지 번호(startPageNum)
전체 페이지 수(totalPages)
페이지네이션 컴포넌트는 startPageNum
, currentPageNum
, totalPages
라는 세 가지 상태 값을 Recoil을 사용해 전역 상태로 관리했습니다. 이를 통해 애플리케이션 내에서 여러 컴포넌트가 동일한 상태 값을 공유할 수 있도록 하였습니다.
startPageNum
은 현재 페이지 번호의 시작 값을 저장하고, currentPageNum
은 사용자가 현재 보고 있는 페이지 번호를 저장하며, totalPages
는 전체 페이지 수를 저장합니다.
import { useRecoilState } from 'recoil';
import {
currentPageNumState,
startPageNumState,
totalPagesState,
} from 'recoil/states';
여기서 useRecoilState
를 사용하여 페이지네이션 관련 상태들을 관리하고 있습니다. 예를 들어, 페이지 번호가 변경되면 해당 상태를 업데이트하여 페이지 내용을 동적으로 변경할 수 있습니다.
페이지네이션에서 중요한 부분은 페이지 번호를 어떻게 표시할 것인지입니다. 여기서는 range
함수로 현재 페이지 범위에 해당하는 번호들을 계산하여 배열로 반환하고, 이를 기반으로 페이지 번호를 출력하였습니다.
const range = (start = 0) => {
return Array.from({ length: 5 }, (_, index) => index + start);
};
이 코드는 start 페이지 번호부터 시작하여 5개의 페이지 번호를 생성합니다.
예를 들어, startPageNum
이 1이면 [1, 2, 3, 4, 5]가 출력되고, startPageNum이 6이면 [6, 7, 8, 9, 10]이 출력됩니다.
페이지 이동은 이전, 다음 버튼을 클릭하여 페이지 범위를 변경하는 방식으로 구현했습니다. 사용자가 이전 버튼을 클릭하면 startPageNum
을 5만큼 감소시키고, 다음 버튼을 클릭하면 5만큼 증가시켜 페이지를 변경합니다.
const handlePrevPage = () => {
if (startPageNum <= 5) return;
setCurrentPageNum(startPageNum - 1);
setStartPageNum(startPageNum - 5);
};
const handleNextPage = () => {
if (startPageNum + 5 > totalPages) return;
setCurrentPageNum(startPageNum + 5);
setStartPageNum(startPageNum + 5);
};
handlePrevPage
와 handleNextPage
함수는 페이지가 범위를 벗어나지 않도록 조건을 설정하여 불필요한 페이지 이동을 막습니다.
if (startPageNum <= 5) return;
현재 페이지 범위 중 시작하는 번호가 5보다 작은 즉, 1-5의 범위라면 prev 버튼은 동작하지 않습니다. 1-5 다음 범위인 6-10, 11-15 등 의 페이지 범위라면 현재 페이지 번호는 현재 시작 페이지 번호에서 (현재 시작 페이지 번호가 6이라면 5로, 11이라면 10으로) -1을 해주고, 이후 시작 페이지 번호를 -5하여 update 해줍니다. (현재 시작 페이지 번호가 6이라면 1로, 11이라면 6으로)
따라서, 위 두 함수는 prev 버튼 클릭 시 이전 범위의 가장 마지막 페이지로, next 버튼 클릭 시 이후 범위의 가장 첫번째 페이지로 이동하는 함수입니다.
페이지 번호를 클릭했을 때 해당 페이지로 이동할 수 있도록 handlePage
함수를 구현했습니다. 이 함수는 클릭한 페이지 번호를 currentPageNum
상태에 저장하여 해당 페이지의 데이터를 로드할 수 있도록 하였습니다.
const handlePage = (e: React.MouseEvent<HTMLElement>) => {
const { id } = e.target as HTMLElement;
setCurrentPageNum(Number(id));
};
Pagination 컴포넌트는 PostList 컴포넌트 내에서 사용됩니다. PostList 컴포넌트는 게시물 리스트와 함께 페이지네이션을 보여주는 역할을 합니다. 전체 페이지 수가 1보다 크면 Pagination 컴포넌트를 렌더링하여 사용자가 페이지를 이동할 수 있도록 합니다.
const PostList = ({ list, totalPages }: { list: IPostPreviewTypes[]; totalPages: number }) => {
return (
<S.List>
{list && list.map(value => (
<PostItem
key={value.id}
id={value.id}
title={value.title}
content={value.content}
hit={value.hit}
comment={value.comment}
likes={value.likes}
/>
))}
{totalPages > 1 && <Pagination />}
</S.List>
);
};
위의 페이지네이션 구현은 기본적인 페이지 이동 기능을 잘 수행하지만, 몇 가지 아쉬운 점과 개선할 부분이 있었습니다.
상태 관리 최적화
Recoil을 사용하여 페이지 번호를 관리하는 것은 좋은 접근이였지만, 페이지 이동과 관련된 상태 관리가 조금 중복되는 느낌이 들었다.
예를 들어, startPageNum
과 currentPageNum
은 사실 서로 밀접하게 연관되어 있어서 두 개의 상태를 각각 관리하는 것보다는 하나의 상태로 합쳐서 관리하면 더 효율적일 것 같다는 생각을 한다.
따라서, currentPageNum
만을 관리하고, 그 값을 기반으로 startPageNum
을 계산하는 방식으로 상태를 통합하면 관리가 간편해지고, 페이지 이동 시 불필요한 렌더링을 줄일 수 있을 것 같다.
불필요한 렌더링 방지
현재는 페이지 번호를 사용자가 클릭할 때마다 전체 페이지네이션을 다시 렌더링한다. 이 방식은 데이터가 많아질수록 성능에 영향을 미칠 수 있다고 생각한다.
따라서, 페이지 번호와 버튼이 불필요하게 리렌더링되지 않도록 React.memo
를 활용하여 컴포넌트를 최적화할 수 있을 것 같다. Pagination 컴포넌트와 관련된 상태가 바뀔 때만 렌더링되도록 하고, 페이지 번호 클릭 핸들러와 같은 함수를 아래와 같이 useCallback
으로 감싸 불필요한 함수 재생성을 방지하는 방법이 효과적일 것 같다.
const handlePage = useCallback((e: React.MouseEvent<HTMLElement>) => {
const { id } = e.target as HTMLElement;
setCurrentPageNum(Number(id));
}, [setCurrentPageNum]);
const handlePrevPage = useCallback(() => {
if (currentPageNum <= 1) return;
setCurrentPageNum(currentPageNum - 1);
}, [currentPageNum, setCurrentPageNum]);
const handleNextPage = useCallback(() => {
if (currentPageNum >= totalPages) return;
setCurrentPageNum(currentPageNum + 1);
}, [currentPageNum, totalPages, setCurrentPageNum]);
현재 구현된 페이지네이션은 기본적인 기능을 충실히 수행하지만, 페이지가 많을 경우 사용자 경험이 제한적일 수 있다고 판단하였습니다. 위와 같은 변경 사항들을 통해 컴포넌트의 상태 관리가 보다 효율적으로 이루어지고, 불필요한 재렌더링을 방지하여 성능을 최적화할 수 있을 것이라고 생각합니다..!
마침 이번에 진행하는 프로젝트에서 페이지네이션이 필수 요구사항이라 이번 기회에 위 요구사항을 반영하여 페이지네이션을 새롭게 개발해보고자 합니다! 😯