[ TIL 221214 ] 페이지네이션 (Pagination) 이란?

ponyo·2022년 12월 14일
0

Today I Learned

목록 보기
26/30

페이지네이션 (Pagination)

API를 통해서 많은 양의 데이터를 가져와 화면에 렌더링해야 하는 경우 자칫하면 성능 문제를 야기할 수 있음

이 때 성늘을 최적화하기 위한 다양한 방법 중 전통적인 방법으로 페이지네이션이 있음

페이지네이션은 데이터의 갯수를 미리 알고 있고 데이터의 추가나 삭제가 자주 일어나지 않을 때 유용

더 나은 경험을 위한 페이지네이션

  • 클릭 가능한 요소의 크기를 크게 제공
  • 현재 페이지 표시
  • 이전, 다음 페이지 링크 제공
  • 페이지 링크 간 간격을 넓힘

페이지네이션 실습

Preview

리액트 프로젝트 생성

npx create-react-app modal-playground --template typescript

패키지 설치

npm i @emotion/styled @emotion/react react-icons
  • dependencies
    • @emotion/react
    • @emotion/styled
    • react-icons

usePagination

src/hooks/usePagination.ts

interface UsePaginationProps {
  count: number;
  page: number;
  onPageChange: (page: number) => void;
  disabled?: boolean;
  siblingCount?: number;
  boundaryCount?: number;
}

const usePagination = ({ count, page, onPageChange, disabled, siblingCount = 1, boundaryCount = 1 }: UsePaginationProps) => {
  const range = (start: number, end: number) => {
    const length = end - start + 1;

    return Array.from({ length }).map((_, index) => index + start);
  };

  const startPage = 1;
  const endPage = count;

  const startPages = range(startPage, Math.min(boundaryCount, count));
  const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count);

  const siblingsStart = Math.max(Math.min(page + 1 - siblingCount, count - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);

  const siblingsEnd = Math.min(Math.max(page + 1 + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : endPage - 1);

  const itemList = [
    "prev",
    ...startPages,
    ...(siblingsStart > boundaryCount + 2 ? ["start-ellipsis"] : boundaryCount + 1 < count - boundaryCount ? [boundaryCount + 1] : []),
    ...range(siblingsStart, siblingsEnd),
    ...(siblingsEnd < count - boundaryCount - 1 ? ["end-ellipsis"] : count - boundaryCount > boundaryCount ? [count - boundaryCount] : []),
    ...endPages,
    "next",
  ];

  const items = itemList.map((item, index) =>
    typeof item === "number"
      ? {
          key: index,
          onClick: () => onPageChange(item - 1),
          disabled,
          selected: item - 1 === page,
          item,
        }
      : {
          key: index,
          onClick: () => onPageChange(item === "next" ? page + 1 : page - 1),
          disabled: disabled || item.indexOf("ellipsis") > -1 || (item === "next" ? page >= count - 1 : page - 1 < 0),
          selected: false,
          item,
        }
  );

  return {
    items,
  };
};

export default usePagination;

Pagination

src/components/Pagination.tsx

import React from "react";
import styled from "@emotion/styled/macro";
import { GrFormPrevious, GrFormNext } from "react-icons/gr";
import { AiOutlineEllipsis } from "react-icons/ai";

import usePagination from "../hooks/usePagination";

interface Props {
  count: number;
  page: number;
  onPageChange: (page: number) => void;
  disabled?: boolean;
  siblingCount?: number;
  boundaryCount?: number;
}

const Navigation = styled.nav``;

const Button = styled.button<{ selected?: boolean }>`
  color: ${({ selected }) => (selected ? "#fff" : "#000")};
  border: 0;
  margin: 0;
  padding: 8px 12px;
  font-size: 16px;
  font-weight: normal;
  background-color: ${({ selected }) => (selected ? "#36dafa" : "#fff")};
  cursor: pointer;
  border-radius: 100%;
  width: 48px;
  height: 48px;

  &:hover {
    background-color: #ccc;
    color: #fff;
  }
  &:active {
    opacity: 0.8;
  }
`;

const Item = styled.li``;

const ItemList = styled.ul`
  margin: 0;
  padding: 0;
  display: flex;
  list-style: none;
  ${Item} + ${Item} {
    margin-left: 8px;
  }
`;

const Pagination: React.FC<Props> = ({ count, page, onPageChange, disabled, siblingCount, boundaryCount }) => {
  const getLabel = (item: number | string) => {
    if (typeof item === "number") return item;
    else if (item.indexOf("ellipsis") > -1) return <AiOutlineEllipsis />;
    else if (item.indexOf("prev") > -1) return <GrFormPrevious />;
    else if (item.indexOf("next") > -1) return <GrFormNext />;
  };

  const { items } = usePagination({
    count,
    page,
    onPageChange,
    disabled,
    siblingCount,
    boundaryCount,
  });

  return (
    <Navigation>
      <ItemList>
        {items.map(({ key, disabled, selected, onClick, item }) => (
          <Item key={key}>
            <Button disabled={disabled} selected={selected} onClick={onClick}>
              {getLabel(item)}
            </Button>
          </Item>
        ))}
      </ItemList>
    </Navigation>
  );
};

export default Pagination;

App.tsx

import React, { useState, useEffect } from "react";
import axios from "axios";
import Pagination from "./components/Pagination";

interface Airline {
  id: number;
  name: string;
  country: string;
  logo: string;
  slogan: string;
  head_quaters: string;
  website: string;
  established: string;
}

interface Passenger {
  _id: string;
  name: string;
  trips: number;
  airline: Airline;
  __v: number;
}

interface Response {
  totalPassengers: number;
  totalPages: number;
  data: Array<Passenger>;
}

function App() {
  const [page, setPage] = useState<number>(0);
  const [totalPages, setTotalPages] = useState<number>(0);
  const [items, setItems] = useState<Array<Passenger>>([]);

  const handlePageChange = (currentPage: number): void => {
    setPage(currentPage);
  };

  useEffect(() => {
    const fetch = async () => {
      const params = { page, size: 10 };
      const {
        data: { totalPages, data },
      } = await axios.get<Response>("https://api.instantwebtools.net/v1/passenger", { params });

      setTotalPages(totalPages);
      setItems(data);
    };

    fetch();
  }, [page]);

  return (
    <div>
      <ul>
        {items.map((item) => (
          <li key={item._id}>{item.name}</li>
        ))}
      </ul>
      <Pagination count={totalPages} page={page} onPageChange={handlePageChange} />
    </div>
  );
}

export default App;

Reference
FastCampus React 강의

profile
😁

0개의 댓글