본문 바로가기

프로젝트/✍️ [전공자들]

[전공자들 14] 리스트 가져오기(with pagination)

게시글 리스트를 가져오기 위해서 페이지네이션 기능을 구현해야 했다. 

다양한 라이브러리가 있지만, 디자인이 마음에 안들기도 하고 공부도 할 겸 직접 만들었다. 

 

버튼 구상

먼저 페이지네이션의 기본 버튼과 기능을 구상했다. 

<<  <  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)}>&lt;&lt;</button>
                    <button className='function f2' onClick={() => callChangePage(pageInfo.startPage - 1)}>&lt;</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)} >&gt;</button>
                    <button className='function f1' onClick={() => callChangePage(pageInfo.allPage)} >&gt;&gt;</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' />&nbsp;{item.goods}&nbsp;&nbsp;
                    <ReplyIcon className='icon' />&nbsp;{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}`)
  }