게시글 리스트를 가져오기 위해서 페이지네이션 기능을 구현해야 했다.
다양한 라이브러리가 있지만, 디자인이 마음에 안들기도 하고 공부도 할 겸 직접 만들었다.
버튼 구상
먼저 페이지네이션의 기본 버튼과 기능을 구상했다.
<< < 1 2 3 4 5 6 7 8 > >>
- 페이지네이션의 범위는 기본 8페이지씩, 한 페이지당 게시글 10개씩 출력.
- 각 범위별 시작 페이지와 끝 페이지(시작페이지+7)를 지정 (startPage :1, endPage: 8), (startPage:9, endPage:16)
- 게시글 전체가 몇 페이지인지 받아와서 만약 끝페이지로 지정한 번호보다 전체페이지가 작으면 끝페이지를 전체페이지로 지정. (예를 들어, 총 19페이지가 나온다면, 가장 마지막 페이지네이션 범위는 17~19 가 될 것임)
1. 숫자버튼 : 누른 버튼의 번호를 페이지 번호로 요청
2. 기능버튼
- << : 현재범위의 시작번호가 1보다 클 때 화면에 노출 / 가장 앞 1페이지를 호출.
- < : 현재범위의 시작번호가 1보다 클 때 화면에 노출 / 이전 페이지네이션 범위로 돌아감. (startPage - 1번 페이지 호출)
- > : 현재범위의 끝번호가 마지막번호보다 작을 때 화면에 노출 / endPage+1번 페이지 호출
- >> : 현재범위의 끝번호가 마지막 번호보다 작을 때 화면에 노출 / 가장 마지막 페이지 호출
백엔드에서는 게시글 리스트를 내려보내 줄 때 Page정보를 담은 PageInfo객체를 만들어 함께 보내준다.
프론트에서는 PageInfo에 따라 버튼을 생성하고, 유저가 클릭한 버튼에 해당하는 페이지를 다시 요청함.
//백엔드 코드
@Override
public Map<String,Object> getArticleListByType(String boardType, int page, int cnt) throws Exception {
Map<String,Object> res = new HashMap<>();
//사용자에게 보이는건 1페이지지만, 사실 0페이지부터이기 때문에 요청받은 page-1페이지를 조회
//mongodb에서 objectId에는 생성일 정보가 담겨있기 때문에 내림차순으로 하면 최신순 조회 가능
PageRequest pageRequest = PageRequest.of(page-1,cnt, Sort.by("_id").descending());
Page<Article> articles = articleRepository.findByBoardType(pageRequest, boardType);
//PageInfo의 현재페이지, 시작페이지, 끝페이지, 전체페이지를 설정
PageInfo pageInfo = new PageInfo();
pageInfo.setCurPage(page);
pageInfo.setAllPage(articles.getTotalPages());
int start = ((page - 1) / 8) * 8 + 1;
int end = start+8-1;
if(end>pageInfo.getAllPage()) {
end = pageInfo.getAllPage();
}
pageInfo.setStartPage(start);
pageInfo.setEndPage(end);
res.put("pageInfo",pageInfo);
res.put("list",articles.getContent());
return res;
}
//프론트 코드
//페이지네이션 컴포넌트 분리
import React, { useState, useEffect } from 'react'
const Pagination = ({ pageInfo, changePage }) => {
const [pageNumbers, setPageNumbers] = useState([])
받은 pageInfo를 통해 버튼 ui 생성
useEffect(() => {
const arr = []
for (let i = pageInfo.startPage; i <= pageInfo.endPage; i++) {
arr.push(i)
}
setPageNumbers(arr);
}, [pageInfo])
//버튼 클릭시 해당하는 페이지를 요청하는 함수 콜백
const callChangePage = (page) => {
changePage(page);
}
return (
<div id='pagination'>
{pageInfo.startPage > 1 &&
<>
<button className='function f1' onClick={() => callChangePage(1)}><<</button>
<button className='function f2' onClick={() => callChangePage(pageInfo.startPage - 1)}><</button>
</>
}
{
pageNumbers.map((item, index) => (
item === pageInfo.curPage ?
/*현재 선택된 버튼 css 표시, 누를 수 없도록 이벤트 버튼 비활성화*/
<button className='curpage' key={index}>{item}</button>
:
<button className='btn' key={index} onClick={() => callChangePage(item)} value={item}>{item}</button>
))
}
{
pageInfo.endPage < pageInfo.allPage &&
<>
<button className='function f2' onClick={() => callChangePage(pageInfo.endPage+1)} >></button>
<button className='function f1' onClick={() => callChangePage(pageInfo.allPage)} >>></button>
</>
}
</div>
)
}
export default Pagination
//리스트를 출력하는 js
const StudyAsList = () => {
const navigate = useNavigate()
const {major,pageNum} = useParams();
const [articles, setArticles] = useState([])
const [render, setRender] = useState(false)
const [pageInfo,setPageInfo] = useState({});
useEffect(() => {
getArticles(major,pageNum)
}, [major,pageNum])
const getArticles=(category,page)=>{
console.log(category,page)
axiosURL.get(`/board/article/list/study/${category}/${page}`)
.then(res => {
setArticles(res.data.list)
setPageInfo(res.data.pageInfo)
setRender(true)
})
.catch(err => console.log(err))
}
//페이지가 변경되었을 때 navigate를 통해 페이지 다시 렌더링
const changePage=(page)=>{
navigate(`/studyAsList/${major}/${page}`)
}
//카테고리가 변경되었을 때 navigate를 통해 페이지 다시 렌더링
const changeMajor=(pmajor)=>{
navigate(`/studyAsList/${pmajor}/1`)
}
return (
<div id='study-as-list' className='main-board'>
{render &&
<>
<MajorSelect type={"공부궁물"} cateInit={major} changeMajor={changeMajor} />
<div className='mode'>
<div>
<Link to={`/boardAsPeed/study/${major}/1`}><PeedIcon className='icon' /></Link>
<ListIcon className='icon cur' />
</div>
</div>
<div className='article-list'>
{articles.length>0?
articles.map((item, index) => (
<Link to={`/articleDetail/${item.id}`} className='article' key={index}>
<div className='a-title'><b>[{item.subject?item.subject:item.middleMajor}]</b> {item.title}</div>
<div className='a-tail'>
<GoodIcon className='icon' /> {item.goods}
<ReplyIcon className='icon' /> {item.commentCnt}
</div>
</Link>
))
:
<p className='empty-p'>등록된 게시글이 없습니다!!</p>
}
</div>
<div className='pagenation'>
<Pagination pageInfo={pageInfo} changePage={changePage}/>
</div>
</>
}
</div>
)
}
export default StudyAsList
🛠 이슈기록
useEffect()를 사용해서 처음 렌더링 시에는 카테고리, 전공게시판에 맞는 리스트의 1페이지를 호출했다.
그 이후 페이지네이션의 각 버튼을 클릭했을 때, 비동기 요청을 보내 리스트만 업데이트 시켰다.
그러자 문제가 생겼는데, 예를들어 7페이지를 보고 있다가 새로고침을 누르면 다시 1페이지로 돌아가는 것이었다..!
생각을 해보니, 새로고침을 할 때마다 useEffect()에 의해 1페이지를 다시 요청하는게 원인같았고,
그래서 페이지네이션을 통해 리스트 요청을 보내는 함수를 호출하지 않고 url파라미터에 페이지를 담아서
navigate로 페이지를 다시 렌더링시키는 방식으로 해결했다.!
const changePage=(page)=>{
navigate(`/studyAsList/${major}/${page}`)
}
'프로젝트 > ✍️ [전공자들]' 카테고리의 다른 글
[전공자들 16] 1차 완성, 그리고 리팩토링과 신규기능개발 계획 (0) | 2024.09.08 |
---|---|
[전공자들 15] 날짜데이터를 어떻게 관리할 것인가 (1) | 2024.03.12 |
[전공자들 13] 좋아요/싫어요 구현하기 (mongodb/react/spring) (1) | 2023.12.17 |
[전공자들 12] 댓글/답글 기능 만들기 (spring/mongodb 내장객체) (0) | 2023.12.16 |
[전공자들 11] 로그인 상태관리(useContext) (1) | 2023.12.10 |