Vue로 블로그 프로젝트를 시작하면서, react로 구현했던 게시판을 다시 리뷰했는데, 정리해놓으면 유용하게 쓰일 것 같다는 생각에 포스팅하게 되었다.
일단 구현한 환경은 react, nodejs, mongoDB인데 코드는 react만 다룰 것이다.
먼저 pagination.jsx를 구현한다. 게시판에 import될 컴포넌트이다.
import React, {useState, useEffect} from 'react'
import axios from 'axios'
import styles from './mylist.module.css'
const Pagination = ({limit, currentPage, onPageChange}) => {
const [pageCount, setPageCount] = useState(0);
const [pages, setPages] = useState([]);
useEffect(() => {
axios.get('/api/team/pagination')
.then(
res =>
{
const length = res.data.length;
const tmpCnt = Math.ceil(length / limit);
setPageCount(tmpCnt);
const tmpPages = Array.from({length: tmpCnt}, (v, i) => i + 1);
setPages(tmpPages);
}
)
.catch( err => console.log(err));
}, [])
if(pageCount == 1) return null;
return (
<nav>
<ul >
{ pages.map(page => {
if(pages.length > 0){
return (
<li
className={currentPage == page ? `${styles.active} ${styles.pageLi}` : styles.pageLi}
key={page}
onClick={() => {onPageChange(page)}}>
{page}
</li>
)
}
})}
</ul>
</nav>
)
}
export default Pagination
props와 state가 각각 어떤 의미인지만 알면 pagination은 끝이다.
- limit : 한 페이지당 나타낼 게시글의 수
- currentPage: 현재 사용자가 보고 있는 페이지 (처음엔 1)
- onPageChange: 사용자가 currentPage가 아닌 다른 페이지를 누를 경우 (2페이지를 누를 경우) 호출되는 함수 => 그 다음 게시글을 불러오고, currentPage를 변경할 것이다.
- pageCount : 전체 페이지가 몇 페이지가 나올지 계산한 state. => 전체 게시글이 12이고 limit가 10일 경우, 12/10 = 1.2인데 ceil함수를 사용하기 때문에 2가 되어 총 2페이지가 나오게 된다.
- pages : 1부터 전체 페이지의 수로 채워진 배열 => pageCount가 2일 경우, pages는 [1, 2]가 들어간다.
이렇게 모든 state을 set하고나면 이제 return을 하는데,
이때 중요한 건 useState는 비동기이기 때문에, 처리가 완료되고 나서 return이 되어야 한다는 것이다. 그래서 중간에 if(pages.length > 0)하면 return한다는 조건을 넣어주었다.
그리고 li의 style은 postcss를 사용하여서 저렇게 들어간 것이기 때문에 postcss를 사용하지 않는다면 신경쓰지 않아도 된다.
그럼 이제 pagination을 포함하는 List.js에서는 어떤 식으로 props를 내려주고, 데이터를 불러오는지 봐야한다.
import React, {useEffect, useState} from 'react'
import axios from 'axios'
import Pagination from './pagination.jsx'
import styles from './mylist.module.css'
const List = () => {
const [listInfo, setListInfo] = useState([]);
const [skip, setSkip] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [limit, setLimit] = useState(10);
useEffect(() => {
{
const body = {
skip: skip,
limit: limit
}
getListData(body);
}, [])
const getListData = (body) => {
axios.post('/api/url', body)
.then(res => setListInfo(res.data.listInfo);
)
.catch(err => alert("정보를 불러오는데에 실패했습니다.");
)
}
const handlePageChange = (page) => {
setCurrentPage(page);
setSkip((page - 1) * limit);
const body = {
skip: (page - 1) * limit,
limit: limit
}
getListData(body);
setCurrentPage(page)
}
return (
<div>
<table>
<thead>
<tr>
<td>제목</td>
<td>날짜</td>
</tr>
</thead>
<tbody>
{ listInfo.map((item, idx) => {
if(listInfo.length > 0){
return(
<tr key={idx}>
<td>{item.title}</td>
<td>{data}</td>
</tr>
)
}
}
)
}
</tbody>
</table>
<Pagination limit={limit} currentPage={currentPage} onPageChange={handlePageChange}/>
</div>
)}
export default List
List에서도 state먼저 살펴보면
- limit: pagination에서 본것과 마찬가지인 의미이다. 설정을 여기서 한다.
- skip: 게시글을 몇번째 게시글부터 불러올지 설정한다. 처음은 당연히 0부터 불러오고 그 후에 사용자가 다른 페이지를 눌러 handlePageChange가 호출되면 그 때 바뀌게 된다.
- listInfo : axios로 불러온 데이터를 넣어서 렌더링 할 변수이다.
- currentPage : pagination에서 본것과 마찬가지인 의미를 가진다.
진행과정
1. useEffect로 컴포넌트가 처음 mount 됐을 때, axios를 이용해서 listInfo 정보를 불러온다. 이때는 post method를 사용하기 때문에 몇번째 게시물부터(skip) 몇개를 불러올지(limit)를 body에 담아서 함께 보낸다. (현재 페이지는 1 페이지)
2. 사용자가 2 페이지를 눌렀을 경우, handlePageChange method가 작동하는데, 몇개를 불러올지는(limit) 그대로지만 몇번째 게시물부터 불러올지가 변한다. skip은 (page - 1) * limit이기 때문에 2 페이지를 누른 경우, 1 * 10으로 10번째 게시글부터 10개의 게시글을 불러오게 된다.
- 여기서 주의할 점은, setState는 비동기적이기 때문에 해당 set을 한 이후로 바로 사용하면 제대로 처리가 안된다. 그렇기 때문에 일단 바꿔는 놓고 param으로 들어온 값을 사용하도록 하자.
3. listInfo를 받아올 경우 map함수를 이용해서 렌더링 하면 된다. 이때도 pagination에서의 렌더링 과정과 같이 해당 state의 length가 0을 초과하면 return하게 한다.