프로젝트를 진행하면서 페이지네이션 기능이 필요해서 페이지네이션을 직접 구현해보았다.
리액트 페이지네이션 라이브러리가 다양하게 있는 것 같은데 그냥 직접 구현해봤다... (라이브러리 쓸 걸 그랬나)
하단 블로그 글을 많이 참고하며 리액트 state에 맞게 바꿔서 구현해봤다.
https://min-kyung.tistory.com/30
[Javascript] 페이지네이션 구현하기
To do list 페이지네이션 구현 json-server를 활용해서 페이지네이션 구현하겠습니다 json-server: https://github.com/typicode/json-server 페이지네이션을 구현하기 위한 설정값은 총 4개가 필요합니다 currentPage:
min-kyung.tistory.com
스타일은 좀 더 수정이 필요하지만 일단 결과물은 이런 식이다.
state들은 다음과 같이 선언해주었다.
- currentPage : 현재 페이지 번호
- totalCount : 보여줄 데이터의 총 개수
- totalPage : 총 페이지 개수
- pageGroup : 현재 페이지 그룹 ( 1 2 3 4 5 => 1그룹 / 6 7 8 9 10 => 2그룹 ... )
- firstNumber : 현재 페이지 그룹의 첫번째 숫자 ( 1 2 3 4 5 )
- lastNumber : 현재 페이지 그룹의 마지막 숫자 ( 1 2 3 4 5 )
- prev : < 버튼을 눌렀을 때 넘어갈 페이지 ( < 6 7 8 9 10 > ==> 5페이지 )
- next : > 버튼을 눌렀을 때 넘어갈 페이지 ( < 1 2 3 4 5 > ==> 6페이지 )
- pageGroupList : 현재 버튼 그룹의 개수 만큼 페이지 버튼을 동적으로 보여주기 위해 번호들을 리스트에 담아서 map함수로 보여줄 때 필요 (ex - 총 8페이지일 경우 페이지 2그룹은 [6 7 8] 3페이지만 존재한다.)
- pageCount : 화면에 나타날 페이지 개수 (5개로 지정)
- limit : 한 페이지 당 나타낼 데이터 개수 (20개 보여줌)
- pageInfo : 페이지 갱신 작업용 객체
여기서 pageInfo가 내가 구현한 페이지네이션에서 가장 중요한 역할을 한다.
WHY?
자바스크립트에서 일반적인 코드를 작성할 때는 synchronous하게(동기적으로) 처리가 된다. 하지만 ajax 통신 등과 같은 특정 작업들이 수행될 때에는 asynchronous하게(비동기적으로) 처리가 된다. 비동기적으로 처리가 될 때는 해당 코드들은 순차적으로 실행되는 것이 아니라 작업이 완료되면 실행된다.
리액트의 state 변경 함수인 setState()는 자바스크립트에서 비동기적으로 처리되기 때문에 페이지네이션 구현 시 순차적으로 이루어지는 작업들의 순서가 꼬일 수 있다.
페이지네이션을 클릭해서 currentPage(현재 페이지)가 바뀌면 아래와 같은 순서대로 작업이 이루어진다.
- 현재 페이지를 화면에 나타낼 페이지 개수로 나눠준 뒤 올림해서 현재 페이지 그룹을 갱신해준다. [ pageGroup = Math.ceil(currentPage / pageCount) ]
- 현재 페이지 그룹이 변경되면 현재 페이지 그룹의 첫번째와 마지막 숫자를 계산해준다. [ lastNumber = pageGroup * pageCount ] [ firstNumber = pageCount * (pageGroup-1) + 1 ]
- firstNumber와 lastNumber가 계산 되면 prev와 next값이 계산된다. [ prev = firstNumber - 1 ] [ next = lastNumber + 1 ]
- 그리고, firstNumber와 lastNumber로 화면에 보여줄 pageCount개수 만큼의 페이지 번호를 리스트(pageGroupList)에 담아준다. ex) [1, 2, 3, 4, 5] | [6, 7, 8]
위의 과정을 순서대로 진행되는 것을 생각하고 리액트의 setState()함수로 코드를 짜면 아래처럼 된다.
// [ currentPage 변경 시 ]
// 현재 페이지 그룹 계산
setPageGroup(Math.ceil(currentPage / pageCount));
// 현재 페이지 그룹의 첫번째와 마지막 숫자를 계산
setLastNumber(pageGroup * pageCount);
if(lastNumber > totalPage){
setLastNumber(totalPage);
}
setFirstNumber(pageCount * (pageGroup-1) + 1);
// prev, next 계산
if(firstNumber === 1){ // prev에 1페이지보다 작은 페이지가 들어가면 안됨
setPrev(1);
} else{
setPrev(firstNumber - 1);
}
if(lastNumber === totalPage){ // next에 전체 페이지보다 큰 페이지가 들어가면 안됨
setNext(totalPage);
} else{
setNext(lastNumber + 1);
}
// pageGroupList 리스트 만들기
var temp = [];
for(var i=first_number; i<=last_number; i++){
temp.push(i);
}
setPageGroupList(temp);
위의 내용을 모두 updatePage() 이름의 함수에 넣고, 해당 함수를 currentPage가 바뀔 때마다 실행되도록 useEffect() 안에 넣어주었다.
하지만 위처럼 코드를 짜면 state의 변경이 의도한 순서대로 이루어지지 않아서 계속해서 페이지가 이상하게 넘어가게 된다.
어쨌든 페이지의 정보가 변경되면 화면을 다시 렌더링 해주어야 하기 때문에 state를 사용해야 한다.
그래서 처음에는 각 state의 갱신 과정을 모두 따로 분리해서 각각의 useEffect()에 넣고 의존성 배열에 이전 단계의 state를 넣어주었다.
하지만 하나의 과정에서도 이전 과정의 state 1개만 사용되는 것이 아닐 때는 또 의도한 순서대로 작업이 이루어지지 않는 것 같았다. 그리고, 서로 연결되어있는 useEffect가 4-5개가 되면서 계속해서 페이지 숫자가 제멋대로 바뀌었다.
그래서 최종적으로 pageInfo라는 state를 사용하면서 다음과 같이 구현하였다.
- 일단 위의 순차척으로 이루어지는 페이지 갱신 작업들을 모두 state가 아닌 var 변수로 처리해주었다.
- 그 다음, 변경된 페이지네이션 값들을 pageInfo라는 객체 state에 세팅해주고, pageInfo에 의존성을 갖는 useEffect()를 실행하면서 해당하는 state값들을 모두 바꿔주었다.
1차적으로는 현재 페이지네이션과 관련된 state값들을 별도의 var변수로 세팅해서 순차적으로 값 변경 작업을 해준 뒤에 pageInfo라는 state에 담아주고, 이 pageInfo의 값들로 기존 페이지네이션 state변수들을 갱신해주는 것이다.
말로 설명하면 어려우니 코드로 확인해보자.
먼저 useEffect()의 의존성 배열에 따라 현재 페이지(currentPage)가 변경되거나, 추천 장소들이 불러와지면(recommendPlaces값이 변경되면) updatePage1() 함수가 실행된다.
// 페이지네이션 갱신 함수
const updatePage1 = () => {
var current_page = currentPage;
var page_group = pageGroup;
var last_number = lastNumber;
var first_number = firstNumber;
var prev_tmp = prev;
var next_tmp = next;
var temp = [];
// 현재 페이지 그룹 계산
page_group = Math.ceil(current_page/pageCount);
// 현재 페이지 그룹 첫번째,마지막 숫자 계산
last_number = page_group * pageCount;
if(last_number > totalPage){
last_number = totalPage;
}
first_number = pageCount*(page_group-1)+1;
// <<< >>> 버튼 값(prev, next) 계산
if(first_number === 1){
prev_tmp = 1;
} else{
prev_tmp = first_number - 1;
}
if(last_number === totalPage){
next_tmp = totalPage;
} else{
next_tmp = last_number + 1;
}
for(var i=first_number; i<=last_number; i++){
temp.push(i);
}
// 변경된 값들에 대한 state 갱신
setPageInfo({
"current_page": current_page,
"page_group": page_group,
"last_number": last_number,
"first_number": first_number,
"prev_tmp": prev_tmp,
"next_tmp": next_tmp,
"page_group_list": temp
})
}
각 state의 기존 값들을 모두 별도의 var 변수에 넣어주고 해당 페이지네이션 변수값들을 순차적으로 갱신해준다.
var 변수값들이 모두 변경되면 pageInfo state에 객체 형식으로 값들을 담아준다.
그러면 pageInfo에 의존성을 갖는 useEffect()가 실행되면서 updatePage2() 함수가 실행되면서 페이지네이션 state값들이 변경된 값으로 세팅된다.
// 페이지네이션 정보 state들 변경된 내용으로 갱신해주는 함수
const updatePage2 = () => {
setPageGroup( pageInfo.page_group );
setLastNumber( pageInfo.last_number );
setFirstNumber( pageInfo.first_number );
setPrev( pageInfo.prev_tmp );
setNext( pageInfo.next_tmp );
setPageGroupList( pageInfo.page_group_list );
}
아래의 글을 참고하면서 문제를 해결했다.
https://codingapple.com/unit/react-setstate-async-problems/
state 변경함수 사용할 때 주의점 : async - 코딩애플 온라인 강좌
(짧아서 글로 진행합니다) 자바스크립트의 sync / async 관련 상식 자바스크립트는 일반적인 코드를 작성하면 synchronous 하게 처리됩니다. 번역하면 동기방식 이런데.. 뭔소리냐면 코드 적은 순서
codingapple.com
그동안 리액트와 리액트 네이티브를 사용할 때 useEffect와 setState의 비동기 처리로 인해 헷갈렸던 부분이 많이 있었다. 항상 구글링을 해봐도 이해가 잘 안갔는데, 이번에 갑자기 이해가 된 것 같다?
위의 코딩애플님 글이 많은 도움이 되었고, 다음에도 비슷한 문제 발생 시에 이런 식으로 var변수를 거치는 방법을 써봐야겠다.
이 방식으로 하되면 중간에 거치는 용으로 중복되는 변수와 state들이 생기기 때문에 사실 좋은 방법은 아닌 것 같다. 더 깔끔한 방법이 있을 것 같은데 빅프로젝트 기한이 촉박해서 일단 되는 방법으로 바로 적용을 시켜보았다.
'React' 카테고리의 다른 글
[ React ] 리액트에서 API KEY 숨기기 (.env) (0) | 2023.01.03 |
---|---|
[ React ] img태그의 src 속성에 props로 require() 전달 시 "Cannot find module" 에러 (0) | 2022.12.26 |
[React] React 프로젝트 git clone으로 받아와서 작업하기 (npm build failed with error code 1 오류 해결) (0) | 2022.12.17 |