보통 게시판을 제작할 때 무한 스크롤 느낌이 아니라면
한번에 보여줄 게시물 개수를 제한하고, 페이지네이션(pagination)을 만들어 이동할 수 있게한다.
어떻게 만들 수 있을까?
limit
이라는 변수. 한 페이지에서 보여줄 게시물의 개수를 의미한다.page
라는 state. 현재 페이지의 번호를 의미한다.offset
이라는 변수. 첫 게시물의 인덱스를 의미한다.46개의 게시물이 존재한다고 가정해보자.
필자는 한 페이지에 10개의 게시물(limit
)을 보여주고자한다.
그렇다면 몇 개의 페이지가 필요할까?
46 / 10 = 4.6
4.6 페이지가 필요하다. 그러나 페이지에 소수점 아래 자리는 있을 수 없으므로
반올림을 해야한다.
따라서, 5 페이지가 필요하다.
각 페이지에는 어떤 게시물들이 보여야할까?
1 페이지에는 0 ~ 9번 인덱스의 게시물이,
2 페이지에는 10 ~ 19번 인덱스의 게시물이,
3 페이지에는 20 ~ 29번 인덱스의 게시물이,
4 페이지에는 30 ~ 39번 인덱스의 게시물이,
5 페이지에는 40 ~ 45번 인덱스의 게시물이 보여야한다.
따라서, 각 페이지의 offset
은
0, 10, 20, 30, 40 이 된다.
여기서 offset
의 식을 유추할 수 있다.
const offset = (page - 1) * limit;
이제 알고리즘을 알았으니 표현해보자.
1 페이지에는 0 ~ 9번 인덱스의 게시물이 보여야한다.
46개의 게시물이 있는 상황에서 이를 어떻게 구현할 수 있을까?
바로 slice
메서드를 활용할 것이다.
페이지 번호와 인덱스의 관계를 따져보면 아래와 같은 코드를 만들 수 있다.
// LecturePage.tsx
lectureList
.slice(offset, offset + limit)
.map(
(item: {
userId: number;
id: number;
title: string;
body: string;
}) => (
// ... //
)
)
lectureList
는 46개의 게시물이 들어있는 배열이다.
slice
메서드를 통해 offset
인덱스부터 offset + limit - 1
번 인덱스까지를 복사한다.
1번 페이지에서는 offset
이 0이었다.
0 + 10 - 1 = 9
따라서 0 ~ 9번 인덱스까지만 사용한다는 것을 알 수 있다.
어? 그러면 다른 페이지에 있는 애들은 어떻게 되는거죠?
잠시 후에 살펴보도록하자.
이번엔 페이지네이션 기능을 해줄 컴포넌트를 만들 것이다.
어떤 것이 필요할지 미리 생각해보자.
이번에는 필요한 것들을 위에서 만든 컴포넌트에서 props로 받아올 것이다.
total
이라는 변수. 게시물의 총 개수를 의미한다.limit
이라는 변수. 한 페이지에서 보여줄 게시물의 개수를 의미한다.page
라는 state. 현재 페이지의 번호를 의미한다.setPage
라는 setState. page
라는 state를 변경할 때 사용한다.페이지네이션 컴포넌트에서 생성하는 것도 있다.
numPages
라는 변수. 페이지의 총 개수를 의미한다.앞서 살펴본 페이지네이션 알고리즘 1에서는
게시물의 개수를 분할하여, 몇 개를 어디에서, 어떤 것부터 보여줄지 정리한 바 있다.
이번에는 페이지네이션 컴포넌트를 제작할 때 어떻게 이 것들을 활용할 것인지 생각해보자.
5 페이지가 필요하니까 아래와 같은 형태로 만들어야겠다.
< 1 2 3 4 5 >
페이지 숫자를 만들기 위해서 게시물의 총 개수(total
)를 알아야한다.
사실 위에서 이미 한번 구해봤다.
46 / 10 = 4.6
4.6 페이지는 없으니까 올림해서 5페이지.
즉, 아래와 같은 식이 나온다.
const numPages = Math.ceil(total / limit);
페이지의 총 개수를 알았으니 이제 더 필요한 것은 없다.
컴포넌트를 만들어보자.
지금까지 얻은 정보들을 활용해서 우리가 만들어야하는 것은
크게 3가지다.
<
버튼. 페이지를 앞으로 이동하는 버튼이다. 즉, 페이지 번호를 감소시킨다.>
버튼. 페이지를 뒤로 이동하는 버튼이다. 즉, 페이지 번호를 증가시킨다.우선 <
, >
버튼부터 만들어보자.
이 버튼들은 어떤 기능을 가져야할까?
페이지의 번호를 줄이고 늘리는 기능을 가져야한다.
그래야 1번 페이지에서 2번, 3번, 4번...페이지를 자유롭게 왔다갔다 할 수 있으니말이다.
우리는 이전 컴포넌트에서 props로 setPage
를 받아왔다.
바로 이 기능을 구현하기 위해서 가져온 것이다.
그리고 페이지가 1보다 더 작아질 수 없게, numPages(최대 페이지 수)보다 커질 수 없게 만들어야한다.
이는 button
태그의 disabled
속성을 이용한다.
페이지 번호가 1이면 <
버튼을 비활성화하고,
페이지 번호가 5(최대)라면 >
버튼을 비활성화하면 된다.
이 기능들을 구현하면 아래와 같다.
// Pagination.tsx
// < 버튼
<button onClick={() => setPage(page - 1)} disabled={page === 1}>
<
</button>
// > 버튼
<button onClick={() => setPage(page + 1)} disabled={page === numPages}>
>
</button>
page
라는 state는 offset
이라는 변수에 영향을 준다.
따라서, 클릭 이벤트가 발생하면 page
가 증가하거나 감소하게되고
이는 offset
을 변경.
결과적으로 페이지에서 보여주는 게시물들이 바뀌게 되는 것이다.
1페이지에서 0 ~ 9번 인덱스가 보여지고 있을 때,
>
버튼을 클릭해서 page
state를 증가시면
offset
은 10으로 변하며, 이는 10 ~ 19번 인덱스를 보여주는 것을 의미한다.
왜냐? 우리는 게시물을 보여주는 페이지에서 slice
를 사용해서
offset
~ offset + limit - 1
의 게시물을 보여주도록 설정했기 때문이다.
이번에는 가장 핵심인 페이지 번호 버튼을 만들어야한다.
1 2 3 4 5
이런 나열된 무엇인가를 보게되면, React를 사용하시는 분들은 아..map 쓰고싶다...
라고 하실 것 같다.
그렇다.
페이지 번호의 배열을 만들어서 map으로 찍어내면 된다.
numPages
개 만큼 원소를 가지는 배열을 만들어주자.
const showedLecture = new Array(numPages).fill(0); // [0,0,0,0,0]
이 예제에서는 5개였으니 0 ~ 4번 인덱스를 가지는 배열이 만들어질 것이다.
이 배열을 가지고 1,2,3,4,5 버튼을 만들어보자.
// Pagination.tsx
{showedLecture.map((item, index) => (
<button
key={index + 1}
onClick={() => setPage(index + 1)}
className={
index + 1 === page
? // 현재 페이지의 버튼 스타일
: // 다른 페이지 버튼 스타일
}
>
{index + 1}
</button>
))}
여기서 주의할 점은 인덱스가 0부터 시작하기 때문에
페이지 표시를 위해서는 index + 1
을 해줘야한다는 것이다.
<
, >
버튼에서의 onClick과 달리 page
를 하나씩 증감하는게 아니라,
특정 페이지로 한번에 이동시키는 것이므로
setPage
에는 index + 1
을 넣어서 offset
을 변경한다.
1번 페이지에서 0 ~ 9번 인덱스인 게시물을 보여주고 있다고 했을 때,
3번 페이지 버튼을 누르면 page
가 3으로 변하게 되고,
이로인하여 offset
이 20으로 변하게 된다.
게시물을 보여주는 컴포넌트에서는 slice
로 인하여
offset
~ offset + limit - 1
까지의 게시물을 보여주므로,
20 ~ 29번 인덱스인 게시물을 보여주는 것으로 바뀌게 된다.