라이브러리없이 페이지네이션 Pagination 구현

단단·2024년 5월 4일
0

프로젝트

목록 보기
1/10

페이지네이션

페이지네이션은 여러 post를 일정 개수로 나눠보여주는 기능'이다.
이에 맞게 페이지 인덱스를 생성하고, 페이지 인덱스 또는 사이드 화살표를 클릭해 해당 페이지로 이동할 수 있다.

구현 설계

페이지네이션을 처음 구현해보는 것이라 일단 구현에 필요한 조건들을 정리하는 게 어려웠다.
필요한 조건은 다음과 같다.

  • 페이지마다 보여줄 post 개수(ITEM_PER_PAGE)
  • 현재 페이지 상태와 현재 페이지 상태를 바꾸는 함수([currentPage, setCurrentPage])
  • 현재 페이지와 상태를 변경할 페이지의 편차값(cardOffset)
  • 총 post 개수(totalPage)
  • 페이지 인덱스 총 길이(limit)
    (oh... 많다...)

사실 처음에 리액트 쿼리를 쓰면 페이지네이션을 쉽게 구현할 수 있다는 정보를 접했다. 그래도 라이브러리없이 구현해보는 게 실력 향상에 나을 것 같아서 리액트 쿼리를 쓰지 않았다.

트러블슈팅

  • 처음에 limit와 offset의 개념이 헷갈려서 값을 바꿔 넣었더니 Post없는 페이지 인덱스가 생겼다. 페이지네이션 구현 조건들의 정확한 개념을 이해하는 게 좀 걸렸다. 그래도 한 번 이해하고 나니 사용하기 괜찮았다.
  • 페이지네이션은 종류가 다양하다. 사이드 화살표를 눌렀을 때 페이지를 이동시킬 것인지 등을 정해야했다. 그래서 기능 명세를 상세하게 작성했다.
limit는 페이지네이션 바에서 보여주는 페이지 인덱스 개수 입니다.
1. FaAngleLeft(왼쪽 화살표):
페이지 인덱스가 1일 때, 페이지 인덱스가 가장 왼쪽이 아닐 때 disabled 된다.
현재 페이지 인덱스가 11이고, limit가 10일 때 해당 버튼 클릭 시 1~10 리스트를 보여준다. 2~9일 땐 disabled 된다.

2. FaAngleRight(오른쪽 화살표):
페이지 인덱스가 limit의 배수가 아닐 때, 페이지 인덱스가 가장 오른쪽이 아닐 때 disabled 된다.
현재 페이지 인덱스가 10이고, limit가 10일 때 해당 버튼 클릭 시 11~20 리스트를 보여준다.

3. 페이지 버튼 클릭 시 해당 버튼을 focus한다.

구현 코드

// main logic
interface PaginationProps {
  totalPage: number;
  limit: number;
  currentPage: number;
  setCurrentPage: SetState<number>;
}

const sliceArrayByLimit = (
  totalPage: number,
  limit: number,
  currentPage: number,
) => {
  const start = Math.max(1, currentPage - limit + 1);
  const end = Math.min(totalPage, start + limit - 1);

  return Array.from({ length: totalPage }, (_, i) => i + 1).slice(
    start - 1,
    end,
  );
};

const Pagination = ({
  totalPage,
  limit,
  currentPage,
  setCurrentPage,
}: PaginationProps) => {
  const [currentPageArray, setCurrentPageArray] = useState<number[]>(
    sliceArrayByLimit(totalPage, limit, currentPage),
  );

  const handlePrevClick = () => {
    const newCurrentPage = Math.max(1, currentPage - limit);

    setCurrentPage(newCurrentPage);
    setCurrentPageArray(sliceArrayByLimit(totalPage, limit, newCurrentPage));
  };

  const handleNextClick = () => {
    const pageGroup = Math.ceil(currentPage / limit);
    const newCurrentPage = limit * pageGroup + 1;

    setCurrentPage(newCurrentPage);
    setCurrentPageArray(sliceArrayByLimit(totalPage, limit, newCurrentPage));
  };

  return (
    <PaginationWrapper>
      <ButtonWrapper>
        <FaAngleLeft
          onClick={handlePrevClick}
          disabled={currentPage === 1 || currentPage !== currentPageArray[0]}
        />
        {currentPageArray.length > 0 &&
          currentPageArray?.map((i) => (
            <PageButton
              key={i}
              onClick={() => setCurrentPage(i)}
              isActive={currentPage === i}
              aria-current={currentPage === i ? 'page' : undefined}
            >
              {i}
            </PageButton>
          ))}
        <FaAngleRight
          onClick={handleNextClick}
          disabled={currentPage === totalPage || currentPage % limit !== 0}
        />
      </ButtonWrapper>
    </PaginationWrapper>
  );
};

export default Pagination;

// 페이지네이션 사용 방법
const ITEM_PER_PAGE = 6;
const [currentPage, setCurrentPage] = useState(1);
const cardOffset = (currentPage - 1) * ITEM_PER_PAGE;

{data && (
          <Pagination
            totalPage={data.count}
            limit={Math.floor(data.count / ITEM_PER_PAGE)}
            currentPage={currentPage}
            setCurrentPage={setCurrentPage}
          />
        )}
profile
반드시 해내는 프론트엔드 개발자

0개의 댓글