페이지네이션 컴포넌트 만들기

지원·2023년 10월 14일
0
post-custom-banner

react(nextjs)로 페이지네이션이 가능한 공통 컴포넌트를 만들어보자.

구현 기능

  1. 페이지 번호 선택하여 페이지 이동
  2. 페이지 수는 10개 단위로 보이게 하기 (1~10 / 11~20 등)
  3. 이전페이지, 다음페이지, 마지막페이지, 첫페이지 이동 등
  • 서버에서 데이터는 아래와 같은 구조로 온다.
export interface ProductsResponse {
  paging: Paging;
  products: Product[];
}

export interface Paging {
  currentPage: number;
  totalPage: number;
}

컴포넌트 구조

컴포넌트 구조는 대략 아래와 같을 것이다.
props로는 currentPage, totalPage, 그리고 버튼 클릭 시 작동시킬 onChangePage 함수를 받으면 된다.

   <컨테이너>
     <제일첫페이지로가기 />    //  1페이지로 이동
     <이전페이지로가기 />     //  current -1 페이지로 이동
     <페이지1 />
     <페이지2 />
     <페이지3 />
     ...
     <페이지10 />
     <다음페이지로가기 />     //  current +1 페이지로 이동
     <제일마지막페이지로가기 /> //  totalPage로 이동
   </컨테이너>

그렇다면 차근차근 만들어보자!

페이지 계산하기

우선 보여줄 페이지 리스트를 계산해야 한다.
예를 들어, totalPage가 17인 경우에는 1~10, 11~17 이렇게 버튼들을 보여줘야 한다.

1. 현재 페이지가 속해있는 페이지 그룹(?)에 몇개의 페이지가 있는지를 계산해야 한다.

  • 전체 페이지 / 10 을 올림한 값이, (현재페이지 -1) / 10을 올림한 값보다 크면 페이지 10개를 다 보여주고,
  • 그 반대일 경우, totalPage / 10의 나머지 개수 만큼 보여줘야 한다. (페이지네이션 마지막 그룹이 된다.)
  const currentPageCount: number =
    Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
      ? 10
      : totalPage % 10;

ex) 전체 페이지를 17이라고 가정하였을 때,
Math.floor(17/10)은 2가 되고,
9페이지를 보여주는 경우 Math.floor(9/10)은 1이 되어 10개 (1~10까지 보여주기)
11페이지를 보여주는 경우 Math.floor(11/10)은 2가 되므로 7개(11~17)를 보여주어야 한다.

2. 해당 페이지 그룹의 페이지 넘버들을 배열로 만들기

  • 1번에서 받은 pageCount 개수대로 0을 채운 배열을 만들고,
new Array(currentPageCount).fill(0)
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • 이 배열을 map을 사용해서 페이지 넘버들을 채울 것이다.
  • 이때 중요한 것은 페이지 그룹에서 제일 작은 페이지 넘버를 찾는 것인데,

1~10 인 경우 1이 되어야 함
11~20 인 경우 11이 되어야 함
21~30 인 경우 21이 되어야 함

  • 현재 페이지에서 1을 빼고, 10을 나눈 것을 올림하면 0, 1, 2와 같이 10단위 수가 나온다.
  • 여기에 다시 10을 곱하고 1을 더해주면 원하는 페이지 그룹 내 첫번째 넘버를 구할 수 있다.
1 + Math.floor((currentPage - 1) / 10) * 10

이를 통해 완성된 currentPages 배열은 아래와 같다.
useMemo를 감싸줌으로써, 불필요한 연산이 계속 발생하지 않게 해둔다.

const currentPages: number[] = useMemo(() => {
  const currentPageCount: number =
    Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
      ? 10
      : totalPage % 10;

  return new Array(currentPageCount)
    .fill(0)
    .map((_, i) => i + 1 + Math.floor((currentPage - 1) / 10) * 10);
}, [currentPage, totalPage]);

changePage 함수 만들기

  • pageChange가 일어났을 때 작동시킬 함수는 props로 받았기 때문에, 해당 page number를 받아 해당 함수를 실행시키는 changePage 함수만 만들어두면 된다.
  const changePage = (page: number) => {
    onPageChange(page);
  };

Button Disabled가 필요한 부분

아래와 같은 경우는 button에 disabled 속성을 주어, 버튼이 활성화되지 않도록 해야한다.

  • 1페이지인 경우 이전페이지로 가기, 제일 첫페이지로 가기
  • 마지막페이지인 경우 다음페이지로 가기, 제일 마지막페이지로 가기

전체 코드

import ArrowLeftIcon from 'assets/icons/ArrowLeftIcon';
import ArrowRightIcon from 'assets/icons/ArrowRightIcon';
import DoubleArrowLeftIcon from 'assets/icons/DoubleArrowLeftIcon';
import DoubleArrowRightIcon from 'assets/icons/DoubleArrowRightIcon';
import { useMemo } from 'react';
import { Container, PageButton } from './styles';

interface PaginationProps {
  currentPage: number;
  totalPage: number;
  onPageChange: (page: number) => void;
}

const Pagination = (props: PaginationProps) => {
  const { currentPage, totalPage, onPageChange } = props;
  const currentPages: number[] = useMemo(() => {
    const currentPageCount: number =
      Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
        ? 10
        : totalPage % 10;

    return new Array(currentPageCount)
      .fill(0)
      .map((_, i) => i + 1 + Math.floor((currentPage - 1) / 10) * 10);
  }, [currentPage, totalPage]);

  const changePage = (page: number) => {
    onPageChange(page);
  };

  return (
    <Container>
      <PageButton onClick={() => changePage(1)} disabled={currentPage === 1}>
        <DoubleArrowLeftIcon width={16} height={16} />
      </PageButton>
      <PageButton
        onClick={() => changePage(currentPage - 1)}
        disabled={currentPage === 1}
      >
        <ArrowLeftIcon width={16} height={16} />
      </PageButton>

      {currentPages.map((page) => (
        <PageButton
          onClick={() => changePage(page)}
          disabled={currentPage === page}
          key={page}
        >
          {page}
        </PageButton>
      ))}
      <PageButton
        onClick={() => onPageChange(currentPage + 1)}
        disabled={currentPage === totalPage}
      >
        <ArrowRightIcon width={16} height={16} />
      </PageButton>
      <PageButton
        onClick={() => onPageChange(totalPage)}
        disabled={currentPage === totalPage}
      >
        <DoubleArrowRightIcon width={16} height={16} />
      </PageButton>
    </Container>
  );
};

export default Pagination;

페이지네이션을 구현할 때에
1. 페이지 그룹 내에 보여줄 페이지 개수,
2. 페이지 그룹 내에서 제일 첫번째 페이지 넘버

이 두가지를 계산하는게 처음에 약간 헷갈렸는데
예시 숫자를 가지고 이리저리 해보다보니 방법을 찾아낼 수 있었다 !

다음 주에는 서버쪽에서 pagination을 하는 내용을 써보고자 한다,,

profile
안녕하세요 지원입니다.
post-custom-banner

0개의 댓글