develop

react-paginate를 이용한 pagination 구현 - 페이지마다 동적 api연결

crab. 2023. 1. 20. 16:16

들어가는 글

  • 게시판에는 글을 보여주는 방식에 크게 두가지가 있다.
    • 무한스크롤
    • 페이지네이션
  • 둘 다 모바일과 데스크탑에 사용해도 상관이야 없지만
  • 보통은 모바일에는 무한스크롤
  • 데스크탑에는 페이지네이션을 사용한다.
  • 그리고 항해 실전프로젝트때 나름 무한스크롤을 꽤나 파봤고
  • 이번에는 페이지네이션을 좀 파봤다.
  • 그래서 정리해본다.

페이지네이션을 위해 우선 알아야 할 것들

  • 우선 내가 이번에 사용한 라이브러리는 react-paginate이며 이유는 나에게 레퍼런스가 이게 많았기 때문이다.
    • react-js-pagination가 다운로드수와 지속적인 업데이트면에서 더 좋으므로 이것을 사용해도 괜찮다.
    • 메소드명만 다르고 기본적인 원리는 같다.
  • 우선 구현사항을 정의해보자.
  • 처음 로딩되면 게시물의 개수에 맞춰 페이지의 숫자들이 페이지목록에 보여야 하며 그 1번페이지의 아이템들이 보여야 한다.
  • 이 페이지목록을 통해 다음페이지나 이전페이지의 숫자를 클릭하면 api통신을 통해 데이터를 가져온다.
  • 이 가져온 데이터를 바탕으로 다음페이지나 이전페이지에 해당하는 아이템들을 렌더링한다.

초기 화면을 위해 서버에게 받아야하는 데이터

  • 페이지네이션을 그리는 페이지가 로딩되면 useEffect를 통해 첫번째 페이지의 아이템을 가져온다.
  • 이때 중요한 건 서버에게 전체 아이템들의 총 개수 또한 받아와야 한다는 점이다.
    • 이 총 개수가 있어야 전체 페이지목록을 그릴 수 있다.
  • 정리하면 나의 경우(redux-toolkit사용)
    • useEffect를 통해 서버로부터 총 아이템개수와 첫번째 페이지의 아이템열개의 배열을 받는다.
    • 이를 각각 eventDueCount와 eventDue라는 이름으로 전역저장소에 저장한다.
    • 그럼 selector를 이용해 페이지네이션을 로딩하는 페이지의 컴포넌트에서 해당데이터를 구독하고 있다가 받아온다.
    • 이 데이터를 reactPaginate에 props를 이용해 전달한다.
// 렌더링하는 컨테이너 컴포넌트
useEffect(() => {
    dispatch(__getEvent());
  }, [dispatch]);
// async에 해당하는 비동기통신
export const __getEvent = createAsyncThunk(
  "get/getEvent",
  async (data, thunkAPI) => {
    try {
      const res = await eventApi.getEvent();
      console.log(res);
      if (res.status === 200) return res.data;
    } catch (err) {
      console.error();
      return thunkAPI.rejectWithValue(err.response.msg);
    }
  },
);
// module에 해당하는 비동기통신
const initialState = {
  isFetching: false,
  errorMessage: null,
  eventDue: [],
  eventDueCount: 0,
};

extraReducers: builder =>
    builder
      // 이벤트 예정, 완료 조회
      .addCase(__getEvent.fulfilled, (state, action) => {
        console.log(action.payload);
        state.eventDueCount = action.payload.event.due.count;
        state.eventDue = action.payload.event.due.rows;
        state.isFetching = false;
        state.errorMessage = null;
      })
// 렌더링하는 컨테이너 컴포넌트
const { eventDue, eventDone, eventDueCount, eventDoneCount } = useSelector(
    state => state.event,
  );
// 렌더링하는 컨테이너 컴포넌트
// Pagination 컴포넌트에 props로 전달
<EventPaginationDue
                items={eventDue}
                count={eventDueCount}
              />

페이지목록을 그리기 위한 로직

  • 이제는 페이지 목록을 위해 앞에서 서버로 부터 받은 아이템개수를 나눠야한다.
  • 즉, 필요한 총 페이지의 수를 알아야 한다.
  • 총 몇 페이지가 필요한지 알기 위해서는 총 게시물 수를 한 페이지 당 표시할 게시물의 수로 나눈 뒤 올림을 하면 몇 개의 페이지가 필요한지를 계산할 수 있다.
  • Math.ceil(총 게시물 수 / 한 페이지 당 표시할 게시물 수)
  • 예를 들어 총 77개의 게시물을 한 페이지당 10개씩 렌더링 해야 한다면, 77 / 10 = 7.7이고, 올림을 하게 되면 8이므로 총 8페이지가 필요하다.
    • (7 페이지까지는 게시물이 10개씩 표시되고 마지막 8 페이지에는 나머지인 7개의 게시물이 표시되어 총 77개가 표시된다.)
const EventPaginationDue = ({ items, itemsPerPage = 10, count }) => {
  const [pageCount, setPageCount] = useState(0);
  const [itemOffset, setItemOffset] = useState(0);
  useEffect(() => {
    setPageCount(Math.ceil(count / itemsPerPage));
  }, [itemOffset, itemsPerPage, items]);

return (
	<ReactPaginate
        nextLabel=">"
        onPageChange={handlePageClick}
        pageRangeDisplayed={5}
        pageCount={pageCount}
        previousLabel="<"
        pageClassName="page-item"
        pageLinkClassName="page-link"
        previousClassName="page-item"
        previousLinkClassName="page-link"
        nextClassName="page-item"
        nextLinkClassName="page-link"
        breakLabel="..."
        breakClassName="page-item"
        breakLinkClassName="page-link"
        containerClassName="pagination"
        activeClassName="active"
        renderOnZeroPageCount={null}
      />
	)
}

페이지가 바뀌면 api통신을 통해 데이터를 가져온다.

  • 이것은 ReactPaginate의 onPageChange속성을 이용하면 된다.
  • 위의 코드에서도 handlePageClick이라는 함수가 해당속성에 연결되어있는것을 확인할 수 있는데(위에서는 혼동을 방지하기 위해 의도적으로 함수의 정의를 뻈다.)
  • 이 함수를 이용하여 페이지 이동을 구현할 것이다.
const handlePageClick = event => {
    const newOffset = (event.selected * itemsPerPage) % count;
    dispatch(__getEventDue(newOffset / 10 + 1));

    setItemOffset(newOffset);
  };
  • (이제 보니 로직이 불필요하게 작성되어 있다.. 리팩토링할 것 투성이이다.)
  • 우선 event.selected를 통해 유저가 선택한 페이지의 숫자를 받아온다.
  • 3을 선택했으면 paginate입장에서는 2이기 때문에(컴퓨터는 0부터 센다.)
  • event.selected는 2가 되고 이게 해당 페이지의 아이템개수인 itemsPerPage에 곱해져서 20이 된다.
  • 이는 다시 10으로 나눠져 2가 되고 나는 서버와 페이지를 1부터 세기로 했기 때문에 1을 더해 3을 보내준다.
.addCase(__getEventDue.fulfilled, (state, action) => {
        state.eventDue = action.payload.event.due.rows;
        state.isFetching = false;
        state.errorMessage = null;
      })
  • 그렇게 보내준 3은 서버를 통해 다시 그에 맞는 아이템의 배열을 받게되고
  • 이 아이템의 배열을 처음 useEffect에서의 전역상태에 저장해준다.(덮어쓰기)
  • 이렇게 하면 동적으로 유저의 페이지 클릭에 맞춰 아이템들을 렌더링할 수가 있다.

끝으로

  • 이렇게 하는 이유는 성능을 최적화하기 위해서이다.
  • 만약 아이템의 개수가 백만개라면? 아니면 일억개라면? 처음에 이 아이템들을 다 받아온다면 엄청난 낭비이지 않을 수 없다.
  • 다시 보니까 코드가 불필요한게 좀 있다.. 수정을 해야겠다.

출처

https://cotak.tistory.com/112

https://velog.io/@tpgus758/리액트-페이지네이션-구현

https://stackoverflow.com/questions/41068815/react-paginate-second-instance-on-same-page-does-not-re-render

https://goddino.tistory.com/218

'develop' 카테고리의 다른 글

Github Action을 이용해서 자동배포하기  (0) 2023.01.20
AWS S3로 프론트엔드(정적 웹사이트) 배포하기  (0) 2023.01.20
refresh token 구현 - axios interseptors  (2) 2023.01.20
axios란?  (0) 2023.01.20
Http, Session, JWT, OAuth  (0) 2023.01.20