페이지네이션은 간단하게 페이지 번호를 클릭해서 다음 페이지 목록을 보여주는 방법이다.
아래는 일반적인 페이지네이션 방법!
import { useQuery, gql } from '@apollo/client';
import { useState } from 'react';
// 1. fetchBoards API를 활용해서 게시글 목록을 불러오기
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
}
}
`;
const FETCH_BOARDS_COUNT = gql`
query {
fetchBoardsCount
}
`;
export default function Pagination() {
const [isColor, setIsColor] = useState(0)
const [startPage, setStartPage] = useState(1)
// 2. 불러온 api 변수에 할당 및 페이지 클릭 시 목록 데이터를 다시 불러 올 수 있게 refetch!
const { data, refetch } = useQuery(FETCH_BOARDS);
console.log(data)
const { data: dataBoardsCount } = useQuery(FETCH_BOARDS_COUNT);
const lastPage = Math.ceil((dataBoardsCount?.fetchBoardsCount ?? 10) / 10);
const onClickPage = (event) => {
void refetch({ page: Number(event.currentTarget.id) })
setIsColor(Number(event.currentTarget.value))
}
// 4. 다음 페이지와 이전 페이지로 이동할 수 있는 코드 작성!
// 5. 페이지가 이동 되면 refetch될 수 있게 설정
const onClickPrevPage = () => {
if (startPage === 1) return;
setStartPage(startPage - 10)
void refetch({ page: startPage - 10 })
}
const onClickNextPage = () => {
if (startPage + 10 < lastPage) {
setStartPage(startPage + 10)
void refetch({ page: startPage + 10 })
}
}
return (
<div>
// 3. map을 이용하여 페이지네이션 뿌리기!
{data?.fetchBoards?.map((el) => (
<div key={el._id}>
<span style={{ margin: "10px" }}>{el.title}</span>
<span style={{ margin: "10px" }}>{el.writer}</span>
</div>
))}
<button style={{ margin: "5px", borderRadius: "10px" }} onClick={onClickPrevPage} disabled={startPage === 1}>{"<"}</button>
{new Array(10).fill("page").map((_, index) => index + startPage <= lastPage && index + startPage >= 1 && (
<button style={{ backgroundColor: isColor === index ? "yellow" : "", borderRadius: "10px", margin: "5px" }} key={index + startPage} id={String(index + startPage)} onClick={onClickPage} value={index}>
{index + startPage}
</button>
))}
<button style={{ margin: "5px", borderRadius: "10px" }} onClick={onClickNextPage} disabled={startPage + 10 > lastPage}>{">"}</button>
</div>
)
}
String, Number, Boolean 같은 경우 각각의 데이터가 변수에 저장된다. 하지만 객체나 배열의 경우 저장해야할 데이터가 많고 데이터의 추가 및 수정이 가능하기 때문에 특정한 주소에 객체나 배열의 데이터가 저장이 된다. 그리고 할당된 변수에 데이터를 바로 저장하는 아니라 주소값이 저장
되어 데이터를 불러 올 수 있다.
따라서 객체나 배열을 복사하여 새로운 변수에 저장하더라도 그 변수에 저장되어 있는 주소값은 같기 때문에 데이터를 수정하면 원본과 수정본 모두 바뀌게 된다.
이러한 문제는 데이터가 저장 될 새로운 주소를 만들어 그 곳에 객체나 배열의 데이터를 저장하면 문제를 해결할 수 있다. 이러한 방법을 스프레드 연산자라고 한다.
let child1 = {
name: "철수",
age: 8,
school: "다람쥐초등학교"
}
// child2에 child1 복사
let child3 = {
name: child2.name,
age: child2.age,
school: child2.school
}
child2 // {name: '영희', age: 8, school: '다람쥐초등학교'}
// 스프레드 연산자를 사용하여 더욱 간편하게 복사
let child3 = {
...child1
}
child3 // {name: '영희', age: 8, school: '다람쥐초등학교'}
스프레드 연산자는 1. 객체나 배열의 데이터 복사, 2. 무한 스크롤 구현 혹은 3. 여러 데이터를 객체로 묶을 때 사용된다.
📌다만 객체 안에 객체가 있는 2단계는 복사가 안된다. 이를 얕은 복사(shallow copy)라고 한다. 이때는 새롭게 다시 객체를 만들어서 복사해주면 된다.
이와 반대로 깊은 복사(deep copy)는 전체 데이터를 복사해 올 수 있다. 깊은 복사는 객체나 배열을 JSON.stringify를 이용하여 문자열로 만들고 JSON.parse()로 다시 객체로 만들어주는 방법이다.
// JSON.stringify
JSON.stringify(profile1)
// '{"name":"철수","age":8,"school":"공룡초등학교","hobby":{"first":"수영","second":"프로그래밍"}}'
// JSON.parse
JSON.parse(JSON.stringify(profile1))
// {name: '철수', age: 8, school: '공룡초등학교', hobby: {first: '수영', second: '프로그래밍'}}
다만 깊은 복사는 시간이 오래 걸릴 수 있는데 npm에서 lodash를 다운로드하면 성능을 최적화 할 수 있다.
무한 스크롤을 구현하기 이전에 아래의 명령어를 통해 다운로드를 해주어야한다!
npm :
npm install react-infinite-scroller
yarn :yarn add react-infinite-scroller
💡 타입스크립트 오류가 발생하는 경우,
yarn add -dev @types/react-infinite-scroller
를 설치
export default function InfiniteScroller() {
const { data, fetchMore } = useQuery(FETCH_BOARDS);
const onloadMore = () => {
if (data === undefined) return
fetchMore({
variables: { page: Math.ceil((data.fetchBoards.length ?? 10) / 10) + 1 },
updateQuery: (prev, { fetchMoreResult }) => {
if (fetchMoreResult.fetchBoards === undefined) {
return {
fetchBoard: [...prev.fetchBoards],
}
}
return {
fetchBoards: [...prev.fetchBoards, ...fetchMoreResult.fetchBoards],
}
}
})
}
return (
<div style={{ height: "500px", overflow: "auto" }} >
<InfiniteScroll pageStart={0} loadMore={onloadMore} hasMore={true} useWindow={false} >
{data?.fetchBoards?.map((el) => (
<div key={el._id}>
<span>{el.title}</span>
<span>{el.writer}</span>
</div>
)) ?? <div></div>}
</InfiniteScroll>
</div>
)
}