Next.js로 페이지네이션 구현

모찌모찌·2024년 4월 17일

✅ 구현 할 것

  1. 한페이지당 보여지는 리스트개수 5개
  2. 한번에 보이는 페이지 갯수 5개
  3. 페이지 클릭시 path쿼리가 변경되어야 한다.
  4. '이전/다음' 버튼 클릭시 이전 페이지, 다음 페이지로 이동
  5. '이전/다음' 페이지가 없으면 버튼 비활성화

✅ 필요한 값들

  • items : 받아오는 데이터

◾ 페이지 네이션 프롭

  • itemCount : 한번에 보여지는 리스트 개수
  • totalCount : api로 받아온 전체 아이템 카운터 갯수

◾ 페이지네이션 컴포넌트에서 쓰이는 변수들

  • totalPages : 전체 페이지 수
const totalPages = Math.ceil(totalCount / itemCount);
  • isFirstPage : 제일 첫페이지
  • isLastPage : 마지막 페이지
const isFirstPage = selectedPage === 1;
const isLastPage = selectedPage === totalPages;
  • handlePageClick : 페이지 클릭시 데이터 처리되는 함수

◾ 전체페이지수를 5페이지씩 보여주는 hook

  • usePagination
    사용 prop : totalPages, page
  • pageNumbers : 현재 페이지가 있는 배열
const [pageNumbers, setPageNumbers] = useState<number[]>([]);

🌊 전체적인 동작 과정

  1. 페이지 네이션컴포넌트를 쓰는 페이지에서 서버사이드 렌더링을 위한 데이터를 가져오는 과정을 거친후 (fetchNoticeData, fetchShopData, fetchTableData 함수들)

  2. 페이지가 로드 될때마다 getServerSideProps 함수에서 위에 정의한 데이터가 비동기처리되어 가져오고 context 를 통해 쿼리에서 page를 가져온다.
    2-2. fetchTableData에서는 페이지네이션을 위해 offset과 - limit을 설정하여 데이터를 가져옵니다.

  3. 각각의 데이터를 가져온 후에는 결과를 처리(이과정은 프로젝트 서버사이드 구현 참고)


📝 페이지네이션 동작 과정

  1. 프롭으로 받아온 totalPages숫자만큼 배열을 만든다.
    1-2sliceNumber에서 정의된 숫자만큼 배열을 자른다.
    1-3 현재 페이지 번호가 속한 배열을 찾아 리턴해준다.
    1-4 리턴받은 페이지 배열을 보여준다.
    예시 : 7페이지 클릭시 ( 6-10까지 보여줌)
// usePagenation.tsx

 const handleSlicePage = useCallback(() => {
    const array = Array.from({ length: totalPages }, (_, i) => i + 1);
    // 자른 페이지 넘버들을 넣는 변수
    const slice: number[][] = [];
    for (let i = 0; i < array.length; i += sliceNumber) {
      // 자르는 로직
      const sliceItem = array.slice(i, i + sliceNumber);
      // 자른 배열을 넣기
      slice.push(sliceItem);
    }
    // eslint-disable-next-line no-restricted-syntax
    for (const row of slice) {
      if (row.includes(page)) {
        setPageNumbers([...row]);
        break;
      }
    }
  }, [totalPages, page]);

  useEffect(() => {
    handleSlicePage();
  }, [page, handleSlicePage]);

  return { pageNumbers };
}
  1. 페이지 클릭시 handlePageClick 함수가 실행되고, 해당 페이지 번호를 쿼리로 변경 후 새로운 데이터 불러온다.
    ❗ 이때 getServerSideProps는 페이지를 불러올때 한번만 실행되는데, shallow 옵션을 false로 설정해서 다시한번 서버 사이드 함수가 실행되게 한다.
예> 2 클릭시 ?page=2 로 이동

handlePageClick(
	setSelectedPage(pageNumber);
    //쿼리를 클릭한 페이지로 변경
    await router.replace(
      {
        pathname,
        query: { ...query, page: pageNumber.toString() },
      },
      undefined,
      {
        shallow: false, // getServerSideProps는 같은 주소(pathname)일 경우 한번만 호출되서 이 옵션을 false로 바꿔서 호출되게 변경
        scroll: false,
      },
    );
)

페이지 수 나타내는 div

 <div className={styles.pageNumberBox}>
          {Array.from({ length: totalPages }, (_, i) => i + 1).map(num => (
            <div
              className={`${styles.pageNumber} ${
                num === selectedPage ? styles.selected : ''
              }`}
              key={num}
              onClick={() => handlePageClick(num)}
            >
              {num}
            </div>
          ))}
        </div>

✅ 페이지 수에 따른 페이지 넘버를 생성하고 매핑하는 부분입니다.

{Array.from({ length: totalPages }, (_, i) => i + 1).map(num => ( ... ))}

Array.from() 메서드를 사용하여 페이지 수 만큼 배열을 생성하고, map() 함수를 사용하여 각 페이지 넘버를 생성합니다.
_는 현재 요소를 나타내며, i는 현재 요소의 인덱스를 나타냅니다. 우리는 인덱스를 1부터 시작하는 페이지 넘버로 변환하기 위해 i + 1을 반환합니다.

Array.from()첫 번째 인자배열로 만들 이터러블한 객체가 되며, 두 번째 인자는 생성한 배열의 모든 원소에 대해 수행할 맵핑 함수입니다. (Array.map() 이라고 생각하시면 됩니다.)

*언더스코어(_) 는 특별한 인자가 아니라, 불필요한 인자의 공간을 채우기 위한 용도입니다.

=> 페이지수가 많아지면 전체페이지가 한줄로 길게 나와서 5개씩 보이도록 코드 수정

<div className={styles.pageNumberBox}>
          {pageNumbers.map(num => (
            <div
              tabIndex={0}
              className={`${styles.pageNumber} ${
                num === selectedPage ? styles.selected : ''
              }`}
              key={num}
              role="presentation"
              onClick={() => handlePageClick(num)}
              onKeyDown={event => {
                if (event.key === 'Enter') {
                  handlePageClick(num);
                }
              }}
            >
              {num}
            </div>
          ))}
        </div>

▶ 다음, 이전 버튼

맨첫페이지와 맨끝페이지는 버튼이 회색이 되게 만든다.

const isFirstPage = selectedPage === 1;
  const isLastPage = selectedPage === totalPages;
  
  ...
  
  {isFirstPage ? (
          <LeftButton className={styles.icon} tabIndex={0} />
        ) : (
          <LeftButtonOn
            className={styles.icon}
            onClick={() => !isFirstPage && handlePageClick(selectedPage - 1)}
          />
        )}
        
{isLastPage ? (
          <RightButton className={styles.icon} tabIndex={0} />
        ) : (
          <RightButtonOn
            tabIndex={0}
            className={styles.icon}
            onClick={() => !isLastPage && handlePageClick(selectedPage + 1)}
          />
        )}
profile
꼬꼬마 개발자 지망생

0개의 댓글