본문 바로가기
챌린지/패스트캠퍼스 공부 루틴 챌린지

패스트캠퍼스 챌린지 36일차 (Pagination)

by 무벅 2022. 2. 28.
반응형

22.2.28 (월) +36days

페이지네이션(Pagination) 이란?

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

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

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


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

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

Preview

리액트 프로젝트 생성

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

패키지 설치

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

dependencies

페이지네이션(Pagination) 이란?

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

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

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


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

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

Preview

리액트 프로젝트 생성

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

패키지 설치

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

dependencies

usePagination

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

const usePagination = ({
  count,
  page,
  onPageChange,
  disabled,
  siblingCount = 1,
  boundaryCount = 1,
}: Props) => {
  // range(2, 5) -> [2, 3, 4, 5]
  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;

  // 1, 2, ... 6, 7, 8, ... 99, 100
  const startPages = range(startPage, Math.min(boundaryCount, count));
  const endPages = range(
    Math.max(count - boundaryCount + 1, boundaryCount + 1),
    count
  );

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

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

  const itemList = [
    'prev',
    ...startPages,
    ...(siblingStart > boundaryCount + 2
      ? ['start-ellipsis']
      : boundaryCount + 1 < count - boundaryCount
      ? [boundaryCount + 1]
      : []),
    ...range(siblingStart, siblingEnd),
    ...(siblingEnd < 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),
          selected: false,
          item,
        }
  );

  return { items };
};

export default usePagination;

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 ? '#3d6afe' : '#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;
  }
`;

export default function Pagination({
  count,
  page,
  onPageChange,
  disabled,
  siblingCount,
  boundaryCount,
}: Props) {
  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>
  );
}

App.tsx

import { useEffect, useState } 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(0);
  const [totalPages, setTotalPages] = useState(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 className="App">
      <ul>
        {items.map((item) => (
          <li key={item._id}>{item.name}</li>
        ))}
      </ul>
      <Pagination
        count={totalPages}
        page={page}
        onPageChange={handlePageChange}
      />
    </div>
  );
}

export default App;

 

 

 

 

 

 

 

https://bit.ly/37BpXiC

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다

 

 

 

 

 

반응형

댓글