부가 라이브러리 사용 없이 React와 NestJS (+TypeOrm)으로 Pagination을 구현해봤다.
우선 백엔드인 NestJS 코드는 이렇다. (Service
만 보겠다)
Controller
에서 queryString으로 page
를 받는다. 우리 게시판은 무조건 게시물을 10개씩만 보여줄 거기 때문에, page
만 받아오면 됐다.
TypeOrm이 제공하는 findAndCount
메서드를 사용했다. React에서 총 게시물 수를 알아야 마지막 페이지에서 버튼 렌더링을 멈출 수 있기 때문.
findAndCount
메서드는 옵션에 맞게 SELECT 구문을 날리고, COUNT까지 해준다.
리턴값이 두개 나오기 때문에 [posts, postCount]
로 구조 분해 할당하여 리턴값을 받아온다.
다른 find
옵션도 있지만 블로그에서는 생략, Paging과 관련된 take
, skip
만 설명하겠다. 이건 TypeOrm의 findAndCount
뿐 아니라, find
계열 메서드에는 모두 붙어있는 옵션이다.
take
는 SQL의 LIMIT으로 바뀐다. (N개만 SELECT하기) 우리는 무조건 10개만 받기로 했기 때문에, 직관적으로 10을 넣어뒀다.skip
은 SQL의 OFFSET으로 바뀐다. (M번째부터 SELECT하기) 한 페이지당 게시물을 10개씩 받아오고, page
값을 queryString으로 받기 때문에, page
= 1 이면 1번째부터, page
= 4 라는 요청을 받으면 31번째 게시물부터 40번째 게시물을 SELECT해야 한다.AppRouter의 서버컴포넌트가 권고하는 fetch 패턴대로 컴포넌트 안에 fetch를 넣었다.
// NextJS 공식문서 코드입니다. 패턴 파악을 위해...
async function getData() {
const res = await fetch('https://api.example.com/...')
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
아래는 작성한 코드. getAllChatPost
함수 안에 GET 요청 fetch가 들어있다.
PostPage
컴포넌트가 처음 렌더링 될 때에 fetch를 자동으로 실행하는 것. 그래서 fetch를 해오는 쪽을 바꾸거나 할 필요가 없었다. 다만, SearchParams 를 사용해서 클라이언트 페이지의 queryString 을 백엔드로 가는 API 안에 그대로 넣기로 했다.
그리고 페이징 버튼마다 <Link>
태그를 달아서, href
안에 queryString만 추가하면 끝.
즉, 코드 작동 순서는 이렇다.
?page=${페이지번호}
가 URL에 붙어있는 <Link>
태그를 누른다.PostPage
컴포넌트의 fetch 함수 실행 → NestJS도 해당 page
데이터를 queryString으로 받아옴.findAndCount
메서드 실행 → DB 커넥션 이후 클라이언트로 데이터 돌려줌PostPage
컴포넌트는 totalPage
값을 계산 (10개씩이니까 Math.ceil(totalCount / 10)
)
currentPage
와 totalPage
는 PostPage
컴포넌트에서 prop으로 내려준다.
pagingButtons
는 길이 5개의 Array로 고정했다. 이 안의 요소마다 index 지정해서 값을 할당하는식으로 구현했다. 이 배열을 state
로 선언하지 않은 이유는, prop으로 받아온 currentPage
와 totalPage
에 따라서 렌더링이 결정되는데, 이것들이 이미 Reactive Value이기 때문이다. State를 참조하는 State를 또 만드는 것은 불필요하고, 받아온 값이 바뀔 때마다 리렌더링이 일어나기 때문에, 굳이 State로 만들 필요는 없다. 게다가 매 리렌더링마다 State를 유지할 필요가 없기 때문이기도 하다.
경우의 수는 총 3가지다.
currentPage
가 1, 2인 경우에는 [<][1] [2][3] [4][5] [>] 를 뿌려주고, currentPage
인 [1] 이나 [2]에 색깔을 바꿔줘야 한다.
currentPage
가 3 이상이면 ex. currentPage
= 7
[<][5] [6][7] [8][9] [>] 이렇게 뿌려주고, 가운데 7에 색깔을 바꿔줘야 한다.
currentPage
가 totalPage
랑 같거나, 1칸 작아지면
ex. totalPage
= 6, currentPage
= 5
[<][2] [3][4] [{{5}}][6] [>]
이렇게 5에 체크를 해줘야 한다.
*e
값은 pagingButtons.map
안의 콜백이 받는 인자다.