[React] react-router-dom 으로 Pagination 기능 만들기

코딩하는 남자·2022년 5월 13일
0

React 정리

목록 보기
7/8
post-thumbnail

🧩 Pagination

react-router-dom 은 리액트 앱 내에서 부드러운 페이지 이동을 구현하게 해주는 라이브러리이다. 이를 응용해서 Pagination 기능을 만들어보았다.

Pagination은 많은 목록을 여러 페이지로 나누어서 정렬하는 기법이다.


📌 완성된 모습


📌 구현한 기능

  • 한 페이지에 최대 5개의 목록을 표시한다.
  • 숫자를 누르면 해당하는 목록을 표시한다. (숫자는 한 페이지 최대 5개)
  • Start - 1 페이지로 이동
  • Prev - 한 페이지 전으로 이동
  • Next - 한 페이지 뒤로 이동
  • End - 끝 페이지로 이동
  • 숫자 1이 목록에 있으면 Start 버튼을 비활성화한다.
  • 마지막 페이지에 해당하는 숫자가 목록에 있으면 End 버튼을 비활성화한다.
  • 이전 페이지가 없으면 Prev 버튼을 비활성화한다.
  • 다음 페이지가 없으면 Next 버튼을 비활성화한다.

🧩 Pagination 에 필요한 함수 만들기

📌 makePagination()

Pagination 기능을 구현하기 위해서 makePagination() 함수를 만들었다.

🧷 매개변수

NameValue
listCnt총 게시물의 개수 (완성본 -> 31)
pageRange숫자 목록에 나타낼 숫자의 범위 (완성본 -> 5)
thisPage현재 페이지

🧷 반환 값

NameValue
startRange현재 페이지 범위의 시작 번호 (숫자 목록에 표시된 처음 번호)
endRange현재 페이지 범위의 끝 번호 (숫자 목록에 표시된 끝 번호)
lastPage마지막 페이지의 번호 (완성본 -> 7)
startIdx현재 페이지의 게시물 첫번째 인덱스
lastIdx현재 페이지의 게시물 마지막 인덱스
showStartBtnStart 버튼을 활성화하는지
showEndBtnEnd 버튼을 활성화하는지
pageNotFound현재 페이지가 게시물의 범위를 벗어나는지

🧷 함수 본문

use-pagination.js

📌 usePagination()

다른 페이지에서도 로직을 재사용할 수 있게 커스텀 hook인 usePagination() 함수를 만들었다. 위에서 만든 makePagination() 함수의 반환값에 페이지 상태 함수와 페이지 이동 함수를 더해서 리턴해준다.

🧷 페이지 이동 함수

함수명기능
goStart()처음 페이지(1)로 이동 (Start 버튼)
goPrev()이전 페이지로 이동 (Prev 버튼)
goNext()다음 페이지로 이동 (Next 버튼)
goEnd()마지막 페이지로 이동 (End 버튼)

🧷 함수 본문


🧩 페이지에 렌더링하기

📌 QuoteList.js

QuoteList.js 에서는 다음과 같은 역할을 수행한다.
(prop 으로 목록(quote) 전체를 받아온다.)

  1. url에 표시된 페이지 정보를 받아온다.

  2. prop으로 받아온 목록의 길이와 현재 페이지 정보를 usePagination() 함수에 매개변수로 넘겨서 리턴값들을 받아온다.

  3. 사용자가 페이지를 이동하면 그에 따라서 실제 페이지를 이동시킨다. (useEffect() 활용)

  4. 현재 페이지의 정보를 활용해서 해당 목록들을 화면에 렌더링한다. (페이지가 목록의 범위를 벗어나면 PageNotFound 컴포넌트를 리턴한다.

  5. usePagination() 에서 받아온 값들을 Pagination 컴포넌트에 prop 으로 넘긴다. (페이지 리모컨은 Pagination 컴포넌트에서 렌더링한다.)

🧷 1. url에 표시된 페이지 정보 받아오기

page 쿼리의 값은 useLocation() 함수로 손쉽게 받아올 수 있다.

import { useLocation } from 'react-router-dom';

const location = useLocation();
const queryParams = new URLSearchParams(location.search);

이제 queryParams.get('page') 함수로 현재 페이지를 받아올 수 있다.


🧷 2. usePagination() 함수 사용하기

prop 으로 받아온 목록의 길이와 페이지 정보를 usePagination() 함수의 매개변수로 넘긴다.

import usePagination from '../../hooks/use-pagination';

 const {
    thisPage,
    setThisPage,
    goStart,
    goPrev,
    goNext,
    goEnd,
    startRange,
    endRange,
    lastPage,
    startIdx,
    lastIdx,
    showStartBtn,
    showEndBtn,
    pageNotFound,
  } = usePagination(
    props.quotes.length, // 목록의 길이
    5, 					 // 숫자 목록에 나타낼 숫자의 범위 
    Number(queryParams.get('page')) || 1 // 현재 페이지 (없으면 1)
  );

🧷 3. 페이지 변경을 실제로 적용하기

사용자가 페이지를 이동하면 usePagination()thisPage state 가 변경된다.
useEffect() 함수로 thisPage 가 변경되는 것을 감지하면 useNavigate() 를 사용해서 실제로 페이지를 이동시킨다.

import { useEffect } from 'react';
import { createSearchParams, useNavigate } from 'react-router-dom';

const navigate = useNavigate();
// useLocation() 에서 현재 url을 받아옴 (쿼리 값 제외)
const { pathname } = location;  

// thisPage 가 변경되면 useEffect() 함수를 실행 (나머지는 종속 변수)
useEffect(() => {
    navigate({
      pathname: pathname,
      search: `?${createSearchParams({
        page: thisPage,
      })}`,
    });
  }, [navigate, thisPage, isSortingAscending, pathname]);

🧷 4. 현재 페이지에 해당하는 목록 렌더링하기

만약 현재 페이지가 목록의 범위를 벗어날 경우 PageNotFound 컴포넌트를 리턴한다. 이를 위해 목록을 렌더링하기 전 if 문으로 한번 확인해준다.
(범위가 벗어난 지 확인하는 값은 usePagination() 함수에서 받아왔다.)

이제 usePagination() 에서 받아온 startIdx, lastIdx 값을 slice() 함수의 인자로 넣어준다.
(간단한 기능이므로 다른 건 생략하겠다.)

5. Pagination 컴포넌트에 prop 넘기기

페이지 리모컨 기능은 Pagination 컴포넌트에서 구현할 것이므로 필요한 기능들을 prop 으로 넘겨준다.

사용자가 버튼을 누르지 않고 숫자를 직접 눌러서 페이지를 이동할 수 있게 onChangePage() 함수를 추가로 생성한다.


📌 Pagination.js

Pagination.js 에서는 페이지 리모컨을 생성한다.
필요한 모든 정보와 기능을 prop 으로 받아왔기 때문에 이제 간단히 구현할 수 있다.


🧷 버튼 표시하기

Start Prev Next End 버튼을 표시하는 코드이다.
(실제로는 숫자 양옆에 표시한다.)

비활성 여부를 결정하기 위해 disabled prop 에 간단한 로직을 추가했다.


🧷 숫자 목록 표시하기

사용자가 숫자를 눌러서 페이지를 이동할 수 있게 해주는 숫자 목록이다.

prop 으로 받아온 startRangeendRange 를 사용해서 리스트를 만들어준다.
만약 현재 페이지가 3페이지면 pages 변수에는 [1,2,3,4,5] 의 값이 들어간다.

이제 생성한 리스트와 map() 메소드를 이용해서 숫자를 렌더링하면 끝이다.
( 현재 페이지에 해당하는 숫자의 색을 다르게 하기 위해서 className prop 에 로직을 추가했다. )


📌 완성된 모습


🧩 마치며

예전에 django 동영상 강의를 보면서 Pagination 기능을 만들어본 적이 있었지만 혼자서 구현해보니 아예 다른 느낌이었다. 어떤 식으로 코드를 짜야할지 처음 설계할 때 부터 많은 고민을 했고 결국에는 커스텀 훅으로 기능을 구현했지만 잘 한건지 아직도 모르겠다.

하지만 이번 프로젝트를 진행하며 확실히 느낀 점은 기능을 구현하는 것보다 useMemo() / useCallback() 으로 앱을 최적화하는 것이 더 어렵다는 점이다. (중간에 시도했다가 결국 다 없애버렸다.)

코드 최적화에 대해 더 공부해야겠다. ^^

끝.

profile
"신은 주사위 놀이를 하지 않는다."

0개의 댓글