페이지네이션을 구현하기 위한 방법
페이지네이션을 구현하기 위해서는 기본적인 두 가지 정보를 알아야 한다.
첫 번째, 총 몇 페이지가 필요한지 알기 위해서는 총 게시물 수를 한 페이지 당 표시할 게시물의 수로 나눈 뒤 올림을 하면 몇 개의 페이지가 필요한지를 계산할 수 있다.
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
- 현재 페이지에 해당하는 게시물들을 보여주는
Posts
컴포넌트 작성
Posts
컴포넌트가 관리하는 상태
1. 페이지 당 게시물 수 : postsPerPage
2. 현재 페이지 번호 : currentPage
그리고 페이지의 첫 게시물의 위치를 담기 위한 변수 offset
를 정의한다.
const [postsPerPage, setPostsPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const offset = (currentPage - 1) * postsPerPage;
참고
게시물 검색 기능을 위해Search
와Posts
컴포넌트를 분리했고, 두 컴포넌트에서 모두 접근할 수 있는 부모 컴포넌트에서 전체 게시물 데이터를 관리하고 있다.
그리고 렌더링할 게시물들은prop
으로Posts
컴포넌트에게 전달된다.
이후 Posts
컴포넌트에서는 현재 페이지에서 렌더링할 게시물들을 계산하여 각각의 게시물을 나타내는 PostItem
컴포넌트를 호출한다.
const totalPosts = props.postList
.slice(offset, offset + postsPerPage)
.map((post) => <PostItem key={post.id} {...post} />);
return (
...
<ul>{totalPosts}</ul>
...
)
- 페이지의 이동을 담당하는
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
컴포넌트 구현
numOfPages
)를 계산한다.const numOfPages = Math.ceil(props.total / props.postsPerPage);
let totalButtons = new Array(numOfPages).fill(null);
totalButtons = totalButtons.map((_, index) => index + 1);
...
return (
...
{totalButtons.map((pageNumber) => (
<Button>
{pageNumber}
</Button>
...
)
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>
))}
//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 페이지라면, 버튼이 동작하지 않도록 하며, 사용자에게 모달
로 이에 대한 알림 메시지를 보여준다.
다음 페이지 버튼 >
을 눌렀을 때 현재 페이지가 마지막 페이지라면 위와 동일한 로직을 추가한다.
//이전 버튼
<Button
type="button"
onClick={props.onClickPage.bind(null, props.currentPage - 1)}
>
<
</Button>
{... 페이지 번호가 담긴 버튼 JSX 코드}
//다음 버튼
<Button
type="button"
onClick={props.onClickPage.bind(null, props.currentPage + 1)}
>
>
</Button>