들어가는 글
- 게시판에는 글을 보여주는 방식에 크게 두가지가 있다.
- 무한스크롤
- 페이지네이션
- 둘 다 모바일과 데스크탑에 사용해도 상관이야 없지만
- 보통은 모바일에는 무한스크롤
- 데스크탑에는 페이지네이션을 사용한다.
- 그리고 항해 실전프로젝트때 나름 무한스크롤을 꽤나 파봤고
- 이번에는 페이지네이션을 좀 파봤다.
- 그래서 정리해본다.
페이지네이션을 위해 우선 알아야 할 것들
- 우선 내가 이번에 사용한 라이브러리는 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에서의 전역상태에 저장해준다.(덮어쓰기)
- 이렇게 하면 동적으로 유저의 페이지 클릭에 맞춰 아이템들을 렌더링할 수가 있다.
끝으로
- 이렇게 하는 이유는 성능을 최적화하기 위해서이다.
- 만약 아이템의 개수가 백만개라면? 아니면 일억개라면? 처음에 이 아이템들을 다 받아온다면 엄청난 낭비이지 않을 수 없다.
- 다시 보니까 코드가 불필요한게 좀 있다.. 수정을 해야겠다.
출처
'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 |