리액트에서 네이버 카페 게시판 같은 Pagination 구현하기

Lenny·2023년 10월 24일
1

이번 포스팅에서는 제가 페이지네이션을 어떻게 구현했는지 기록해보려고 합니다!

참고로 보여지는 데이터는 fetch 데이터가 아닌, 정적인 데이터를 기준으로 합니다!
만약 fetch로 받아오는 데이터를 기준으로해도 로직은 크게 다르지 않을겁니다! (참고가 되실거에요!)

결과물 미리 감상

이번에 구현 해 볼 페이지네이션은 다음과 같은 페이지네이션입니다!
(보여지는 데이터는 신경쓰지마셔요!! 제가 데이터를 매우 부족하게 만들어놓아서, 출력이 안되는 문제가 있습니다! 데이터와 상관없이 총 페이지 값만 101로 늘려놓았어요!)

네이버 카페 게시판에 구현된 페이지네이션을 참고하여 구현해보았습니다!
그럼 시작해볼까요!

시작

변수 부분

본격적으로 로직을 구현하기 전, 우리는 몇가지 변수 또는 State가 필요합니다.
이 변수들이 사실 페이지네이션 기능의 핵심이라고 생각해요!

const [currentPage, setCurrentPage] = useState<number>(1);
const limit = 10;
const offset = (currentPage - 1) * limit;
const totalDatas = DATA.length;
let numPages. = Math.ceil(totalDatas / 10);
let pages = [];
let firstPageNum = currentPage - ((currentPage - 1) % 10);
let lastPageNum = firstNum + 9;

for (let i = numPages; i > 0; i--) {
    pages.push(i);
  }

우선 하나씩 설명을 해드리겠습니다.

currentPage State는 현재 페이지를 나타내는 State입니다.

limit 변수는 한 페이지 당 보여줄 개수를 의미합니다. (저는 10개씩 보여줄거라서 10으로 정했어요.)

offset 변수는 추 후 DATA를 slice 할때 사용 될 변수에요. 데이터를 10개씩 잘라서 보여줄 때 사용되요.

totalDatas 변수는 우리가 출력할 데이터의 총 길이를 의미하는 변수에요.

numPages 변수는 우리의 데이터를 기준으로 나오는 페이지 수를 의미해요. 저는 한 페이지에서 보여줄 데이터를 10개로 기준잡아서 Math.ceil(totalDatas / 10) 으로 작성했어요. Math.ceil(totalDatas / <보여질 데이터의 숫자>) 로 커스텀 할 수 있어요.

pages 변수는 페이지를 배열로 담고있는 변수에요.
만약 데이터가 31개라면 저 배열에는 [1, 2, 3, 4] 가 저장돼요.

firstPageNum, lastPageNum 이 두 변수는 페이지네이션의 구간과 관련이 있어요.
현재 위 GIF를 보시면 초기에 1 ~ 10 을 보여주고, 다음 버튼을 누르면 11 ~ 20을 보여주죠. 이런느낌으로 구간을 설정할 때 사용한 변수에요.
저는 10개의 구간을 설정했기 때문에 코드를 이렇게 작성했지만, 구간을 다르게 설정하려면 이 부분을 커스텀하면 되요.

for 블록 코드는 numPages를 기준으로 pages에 페이지를 삽입하는 코드에요!

함수 부분

이제 함수에 대해서 설명을 드릴건데, 페이지네이션 기능에서 우리가 만들 함수는 너무 간단해요.
우리는 그저 페이지를 변경하는 함수만 만들면 돼요.
제가 만든 함수는 다음과 같아요.

  const onClickPageHandler = (page: number) => {
    setCurrentPage(page);
  };

  const prevPageButtonHandler = () => {
    if (firstNum === 1) return;
    setCurrentPage(firstNum - 10);
  };

  const nextPageButtonHandler = () => {
    if (numPages === lastNum) return;
    if (numPages < lastNum) return;
    setCurrentPage(lastNum + 1);
  };

onClickPageHandler 함수는 page를 인자로 받아서 해당 페이지로 변경하는 함수에요.

prevPageButtonHandler 함수는 이전 버튼을 눌렀을 때 실행되는 함수에요.
현재 페이지의 구간이 1 ~ 10 구간일때는 함수가 실행되지 않고, 그 외 구간일때는 이전 구간의 첫번째 페이지로 currentPage가 변경돼요. (시각적인 예시는 위 GIF에서 확인하세요!)

nextPageButtonHandler 함수는 다음 버튼을 눌렀을 때 실행되는 함수에요.
총 페이지 수가 마지막 구간과 같을 때는 다음이 없으므로 함수가 실행 안되게 했어요.
총 페이지 수가 마지막 구간보다 작으면 함수가 실행이 안되게했어요. 이 경우는 다음과 같은 경우에요.
마지막 구간이 110 인데, 총 페이지 수는 101 인 경우,
102 ~ 109 페이지는 존재하지 않으므로 물론 다음 구간도 존재하지 않겠죠?

출력 부분

출력 부분 코드입니다. 여기서 우리가 정의했던 변수, 함수들이 사용돼요!

데이터 출력 부분

            <ul className='log-list'>
              {DATA.slice(offset, offset + 10).map((item) => {
                return (
                  <li
                    className={ClassNameHelper.concat(
                      'log-list__item',
                      item.checked ? 'log-list__item--checked' : null,
                    )}
                    key={item.id}
                  >
                    <div className='log-list__item--type'>
                      <span>{item.type}</span>
                    </div>
                    <div className='log-list__item--message'>
                      <p className='log-list__item--message__text'>
                        {item.message}
                      </p>
                      <span className='log-list__item--message__date'>
                        {item.date}
                      </span>
                    </div>
                    <div className='log-list__item--kebab'>
                      <KebabMenuIcon
                        onClick={() => {
                          KebabMenuHandler(item.id);
                        }}
                        className='log-list__item--kebab-svg'
                      />
                      {KebabToggle === item.id ? (
                        <div className='log-list__item--kebab--menu'>
                          <button>확인하기</button>
                        </div>
                      ) : null}
                    </div>
                  </li>
                );
              })}
            </ul>

위 코드에서 중요한 부분은
DATA.slice(offset, offset + 10) 이 부분이에요.

우리가 선언한 offset을 기준으로 데이터를 잘라서 보여줘요.

const offset = (currentPage - 1) * limit;

자바스크립트 slice 메소드 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

페이지 출력 부분

       <div className='pagination-container'>
          {firstPageNum === 1 ? null : (
            <button onClick={prevPageButtonHandler}>이전</button>
          )}
          {pages
            .sort((a, b) => a - b)
            .slice(firstPageNum - 1, lastPageNum)
            .map((page) => {
              return (
                <span
                  className={ClassNameHelper.concat(
                    'pagination-number',
                    currentPage === page ? 'colored' : null,
                  )}
                  key={page}
                  onClick={() => {
                    onClickPageHandler(page);
                  }}
                >
                  {page}
                </span>
              );
            })}
            {numPages === lastPageNum || numPages < lastPageNum ? null : (
              <button onClick={nextPageButtonHandler}>다음</button>
            )}
        </div>

이 부분은 하단에 페이지를 출력하는 부분이에요.

우선 저는 pages 배열을 순서대로 정렬하기 위해 sort 메소드를 먼저 사용해 주었고, 그 후 구간 별로 잘라서 페이지를 보여줘요.

그리고 "이전" 버튼과 "다음" 버튼을 상황에 맞춰서 숨기거나 보이도록 코딩했어요.
숨길때의 로직은, 함수 부분의 로직을 그대로 가져왔어요.

로직이 겹치니까 둘 중 하나만 구현하면 되지 않았을까요?
-> 혹시.. 몰라서... 그래도 확실한 게 좋으니까 이렇게 작성했어요!

마무리

최대한 자세하게 적어보고싶었지만 설명을 잘 못해서.. 페이지네이션을 구현하려는 분들에게 많은 도움이 될 지는 모르겠네요!
저는 이렇게 구현했다는걸 공유하고 싶었어요!
구현하려는 분들에게 도움이 됐으면 좋겠네요~ ~

참고 링크

profile
🧑‍💻

0개의 댓글