페이지네이션

게시판에서 글이 10000개면 모두 렌더링 할 수 없고 나눠서 렌더링해야 한다.

이렇게 나눠서 다음 또는 이전 페이지로 이동하거나 특정 페이지로 이동할 수 있게 하는걸 페이지네이션이라고 한다.

리액트로 페이지네이션을 구현하기 위해서는 직접 구현하는 방법이 있고 라이브러리를 사용하는 방법이 있다.

라이브러리를 쓰는 방법도 알아야 하겠지만, 지금은 공부용이니 직접 만들어 봤다.

 


페이지네이션 구현

구현할 부분은 페이지네이션의 페이지 부분이다.

이전, 다음 페이지로 이동하는 버튼과 특정 페이지로 이동하는 버튼을 구현하려한다.한 번에 몇 개의 페이지 버튼을 렌더링할지도 정해야하고 총 데이터의 개수에 따라 페이지도 달라진다.

만약 위 사진처럼 한 번에 보여 줄 페이지 수가 5개라면, DB에 13개의 데이터가 있고 한 페이지에 5개의 데이터를 출력하면 페이지는 3개이다. DB에 50개의 데이터가 있고 한 페이지에 5개의 데이터를 출력하면 페이지는 5개이다.또한 이전 페이지 혹은 이후 페이지가 없으면 각각의 화살표 버튼을 비활성화 한다.

pagination.js

페이지네이션에 필요한 유틸 함수들을 모아놓은 js 파일이다.

매개변수로 받을지, 지정된 값을 사용할지 고민이 됐지만 우선은 지정된 값을 사용하기로 했다.

// 한 번에 보여줄 페이지 수
const PAGE_ARRAY_LIMIT = 5;

// 입력 받은 페이지를 기준으로 화면에 출력해줄 페이지 목록을 배열로 반환
// currentPage: 입력받은 페이지
// count: DB에 저장된 데이터 수
// limit: 화면에 렌더링 할 데이터 개수
export function getCurrentPageArray(currentPage, count, limit) {
  currentPage = currentPage >= 1 ? currentPage : 1;
  const totalPage = calculateTotalPage(count, limit);
  const startIndex = getStratIndex(currentPage);
  const length = getCurrentPageArrayLength(startIndex, currentPage, totalPage);

  const arr = length > 0 ? new Array(length).fill(0) : [];

  return arr.map((x, i) => {
    if (i < PAGE_ARRAY_LIMIT) {
      return startIndex + i;
    }
  });
}

// 전체 페이지 계산
function calculateTotalPage(count, limit) {
  return Math.ceil(count / limit);
}

// 시작 인덱스 계산 (1, 6, 11, 16, ...)
function getStratIndex(currentPage) {
  currentPage = currentPage >= 1 ? currentPage : 1;
  return (
    Math.floor((currentPage - 1) / PAGE_ARRAY_LIMIT) * PAGE_ARRAY_LIMIT + 1
  );
}

// 입력 받은 페이지를 기준으로 반환해야 할 페이지 목록 길이 계산
function getCurrentPageArrayLength(startIndex, currentPage, totalPage) {
  if (currentPage > totalPage) {
    return 0;
  }

  if (startIndex + PAGE_ARRAY_LIMIT - 1 <= totalPage) {
    return PAGE_ARRAY_LIMIT;
  } else {
    return totalPage - startIndex + 1;
  }
}

pagination.jsx

pagination.js 유틸 함수들을 이용하여 페이지네이션을 구현한다.

페이지네이션하는 부분만 따로 구현한거라 데이터 렌더링하는 부분과 합치면 불필요한 부분이 있을 수 있다.

function Pagination({ limit, initPage, onClick }) {
  const [searchParams, setSearchParams] = useSearchParams();
  const pageParam = searchParams.get("page");
  const page = Number(pageParam >= 1 ? pageParam : initPage);
  const offset = (page - 1) * limit;
  const { data } = useQuery(subjectListUrl(limit, offset), { data: [] });

  const { count, next, previous } = data;
  const currentPageArray = getCurrentPageArray(page, count, limit);

  const handlePageClick = (pageIndex) => {
    onClick(pageIndex);
    setSearchParams({ page: pageIndex });
  };

  const handlePrevClick = () => {
    handlePageClick(page - 1);
  };

  const handleNextClick = () => {
    handlePageClick(page + 1);
  };

  return (
    <PaginationWrapper>
      <PrevButton onClick={handlePrevClick} disabled={previous === null} />
      <PageButtonWraaper>
        {currentPageArray &&
          currentPageArray.map((idx) => (
            <PageButton
              key={idx}
              pageIndex={idx}
              onClick={handlePageClick}
              page={page}
            />
          ))}
      </PageButtonWraaper>
      <NextButton onClick={handleNextClick} disabled={next === null} />
    </PaginationWrapper>
  );
}

function PageIndex({ className, pageIndex, onClick, page }) {
  const navigate = useNavigate();
  const isNow = page == pageIndex;

  const handleClick = () => {
    onClick(pageIndex);
    navigate(`/list?page=${pageIndex}`);
  };

  return (
    <button
      className={`${className} ${isNow ? "now" : ""}`}
      onClick={handleClick}
      disabled={isNow}
    >
      {pageIndex}
    </button>
  );
}

const PaginationWrapper = styled.div`
  페이지네이션 래퍼 스타일
`;

const PageButtonWraaper = styled.div`
  페이지 버튼 래퍼 스타일
`;

const PageButton = styled(PageIndex)`
  페이지 버튼 스타일
`;

export default Pagination;

 

'프론트엔드 > 연습' 카테고리의 다른 글

Next.js 폴더 구조  (0) 2024.03.11
쿼리 스트링을 이용하여 모달 상태 관리  (0) 2024.01.27
6주차 코드 리뷰 후기  (0) 2024.01.03
반응형 웹 사이트 만들기  (0) 2023.12.30
input 태그의 값 이용하기  (0) 2023.12.19

+ Recent posts