리액트 페이지네이션 구현

김세현·2022년 8월 1일
2

React

목록 보기
8/10

페이지네이션을 구현하기 위한 방법

페이지네이션을 구현하기 위해서는 기본적인 두 가지 정보를 알아야 한다.

  1. 필요한 총 페이지의 수를 알아야 한다.
  2. 현재 페이지 번호를 기준으로 표시해야 할 게시물들의 범위를 알아야 한다.
    즉 해당 페이지의 첫 게시물의 위치(인덱스)를 알아야 한다.

첫 번째, 총 몇 페이지가 필요한지 알기 위해서는 총 게시물 수를 한 페이지 당 표시할 게시물의 수로 나눈 뒤 올림을 하면 몇 개의 페이지가 필요한지를 계산할 수 있다.

Math.ceil(총 게시물 수 / 한 페이지 당 표시할 게시물 수)

예를 들어 총 77개의 게시물을 한 페이지당 10개씩 렌더링 해야 한다면, 77 / 10 = 7.7이고, 올림을 하게 되면 8이므로 총 8페이지가 필요하다.
(7 페이지까지는 게시물이 10개씩 표시되고 마지막 8 페이지에는 나머지인 7개의 게시물이 표시되어 총 77개가 표시된다.)

이번 게시판 구현 사전과제에서 사용하는 API에는 총 100개의 게시물 데이터가 담겨 있고, 페이지 당 10개의 게시물을 렌더링해야 한다.

따라서 Math.ceil(100 / 10) = 10이므로 총 10 페이지가 필요하다.

두 번째,
특정 페이지를 기준으로 표시해야 할 게시물들의 범위를 알아야 한다.
이를 알기 위해선 우선 해당 페이지의 첫 게시물의 인덱스를 알아야 한다.

특정 페이지의 첫 게시물의 인덱스를 알기 위해선 해당 페이지에서 1을 뺀 뒤에, 페이지당 표시할 게시물의 수를 곱해주면 된다.

(해당 페이지 번호 - 1) * 페이지당 표시할 게시물 수

예를 들어 총 77개의 게시물이 있고, 페이지당 표시해야 할 게시물이 10개라면
각 페이지의 첫 게시물의 인덱스는 다음과 같다.
첫 페이지의 첫 게시물의 인덱스 : (1-1) * 10 = 0
두 번째 페이지의 첫 게시물의 인덱스 : (2-1) * 10 = 10
세 번째 페이지의 첫 게시물의 인덱스 : (3-1) * 10 = 20


페이지네이션 구현


  1. 현재 페이지에 해당하는 게시물들을 보여주는 Posts 컴포넌트 작성

Posts 컴포넌트가 관리하는 상태
1. 페이지 당 게시물 수 : postsPerPage
2. 현재 페이지 번호 : currentPage

그리고 페이지의 첫 게시물의 위치를 담기 위한 변수 offset를 정의한다.

  const [postsPerPage, setPostsPerPage] = useState(10);
  const [currentPage, setCurrentPage] = useState(1);
  const offset = (currentPage - 1) * postsPerPage;

참고
게시물 검색 기능을 위해 SearchPosts컴포넌트를 분리했고, 두 컴포넌트에서 모두 접근할 수 있는 부모 컴포넌트에서 전체 게시물 데이터를 관리하고 있다.
그리고 렌더링할 게시물들은 prop으로 Posts 컴포넌트에게 전달된다.

이후 Posts 컴포넌트에서는 현재 페이지에서 렌더링할 게시물들을 계산하여 각각의 게시물을 나타내는 PostItem 컴포넌트를 호출한다.

const totalPosts = props.postList
    .slice(offset, offset + postsPerPage)
    .map((post) => <PostItem key={post.id} {...post} />);


return (
 	...
   <ul>{totalPosts}</ul>
  	...
)
  1. 페이지의 이동을 담당하는 Pagination 컴포넌트 구현

페이지를 이동할 수 있도록 해주는 Pagination 컴포넌트는 이전 페이지나 다음 페이지 또는 특정 페이지로 바로 이동할 수 있는 버튼들로 구성된다.

또한 Pagination에서 필요한 데이터는 Posts 컴포넌트로부터 prop을 통해 받아온다.
전달되는 prop 목록

  • 전체 게시물의 수 : total
  • 페이지 당 게시물 수 :postsPerPage
  • 현재 페이지 번호 : currentPage
  • 현재 페이지를 변경하는 함수 : onClickPage
//Posts 컴포넌트에서 Pagination 컴포넌트를 호출하는 코드
return (
       <>
          <ul>{totalPosts}</ul>
          <Pagination
            totalPosts={props.postList.length}
            postsPerPage={postsPerPage}
            currentPage={currentPage}
            onClickPage={setPage}
          />
       </>
)

Pagination 컴포넌트 구현

  1. 전체 게시물의 수에 따라 필요한 페이지의 수(numOfPages)를 계산한다.
const numOfPages = Math.ceil(props.total / props.postsPerPage);
  1. 페이지의 수만큼 루프를 돌면서 페이지가 번호가 적혀있는 버튼을 출력한다.
let totalButtons = new Array(numOfPages).fill(null);
totalButtons = totalButtons.map((_, index) => index + 1);

		...
        
return (
  	...

      {totalButtons.map((pageNumber) => (
        <Button>
          {pageNumber}
  		</Button>
  	...
)
  1. 상위 컴포넌트 Posts에서 정의한 현재 페이지 상태를 변경하는 함수를 Pagination 컴포넌트에게 prop으로 전달한다.
//Posts.tsx에 작성된 현재 페이지를 변경하는 로직
const [currentPage, setCurrentPage] = useState(1);

...

function setPage(page: number) {
    setCurrentPage(page);
}
...

return (
       <>
          <ul>{totalPosts}</ul>
          <Pagination
            ...
            onClickPage={setPage} //prop으로 전달하기
          />
        </>
)

4.prop으로 받아온 함수를 Pagination 컴포넌트의 버튼 이벤트에 연결한다.

//Pagination.tsx
{totalButtons.map((pageNumber) => (
        <Button  
          onClick={props.onClickPage.bind(null, pageNumber)}>
          {pageNumber}
  		</Button>
 ))}
  1. 현재 보고있는 페이지를 표시(포커스)하기 위해 동적 스타일링을 적용한다.
//Pagination.tsx
{totalButtons.map((pageNumber) => (
        <Button  
          className={`${styles["btn"]} ${
            props.currentPage === pageNumber && styles["focus"]
          }`}
          onClick={props.onClickPage.bind(null, pageNumber)}>
          {pageNumber}
  		</Button>
 ))}
  1. 이전 페이지와 다음 페이지 버튼 구현하기
  • 이전 페이지 버튼 <을 눌렀을 때, 페이지는 현재 페이지에서 1이 감소해야 한다.
    반대로, 다음 페이지 버튼 >을 눌렀을 때에는, 현재 페이지에서 1이 증가해야 한다.

  • 이전 페이지 버튼 <을 눌렀을 때, 현재 페이지가 1 페이지라면, 버튼이 동작하지 않도록 하며, 사용자에게 모달로 이에 대한 알림 메시지를 보여준다.
    다음 페이지 버튼 >을 눌렀을 때 현재 페이지가 마지막 페이지라면 위와 동일한 로직을 추가한다.

//이전 버튼
<Button
        type="button"
        onClick={props.onClickPage.bind(null, props.currentPage - 1)}
>
 &lt;
</Button>

{... 페이지 번호가 담긴 버튼 JSX 코드}

//다음 버튼
 <Button
        type="button"
        onClick={props.onClickPage.bind(null, props.currentPage + 1)}
 >
  &gt;
</Button>

구현된 모습


profile
under the hood

0개의 댓글