페이지 처리를 하는 방법에는 크게 일반적인 방식과 무한스크롤 방식, 2가지 방법이 있다. 각각의 방법에 대해서 알아보자.
페이지 번호를 클릭해서 이동하는 방식의 페이지 처리 방법
페이지네이션은 콘텐츠를 웹사이트의 또 다른 페이지들로 분리하는 방법이다. 사용자는 페이지 하단에 있는 숫자 형식의 링크를 클릭하여 페이지들을 탐색할 수 있고, 페이지네이션된 콘텐츠 일반적으로 몇 가지 공통된 주제 혹은 목적들을 지니고 있다.
페이지 넘버는 일일히 숫자를 써주는 것이 아닌 Array, map, index를 사용해 만든다.
import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
// Page를 받기위해 variables 로 넣어준다.
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const MyRow = styled.div`
display: flex;
flex-direction: row;
`;
const MyColumn = styled.div`
/* width: 25%; */
`;
export default function MapBoardPage() {
const { data, refetch } = useQuery(FETCH_BOARDS);
const onClickPage = (event) => {
// 객체 형태로 refetch 안에 바인딩해준다. event.target.id; 텍스트라 숫자 Number로 바꿔주기
refetch({ page: Number(event.target.id) });
// event.target.id 실재 우리가 어떤 것을 클릭했는지 알 수 있는 부분
};
return (
<div>
{data?.fetchBoards.map((el) => (
<MyRow key={el._id}>
<MyColumn>{el.writer}</MyColumn>
<MyColumn>{el.title}</MyColumn>
</MyRow>
))}
{/* 📌 방법 3 */}
{/* new Array(10) : 비어있는 배열 10개 생성됨*/}
{/* new Array(10).fill(아무숫자): fill 을 해서 10에 "아무숫자"를 채워줌 */}
{/* 1,2,3,4,5,6,7,8,9,10 을 채우고 싶다면? */}
{/* 비어있는 배열 10개만 만들어도 index+1을 해주면 된다.*/}
{new Array(10).fill(1).map((_, index) => (
<span key={index + 1} onClick={onClickPage} id={String(index + 1)}>
{index + 1}
</span>
))}
{/* 📌 방법 2 */}
{/* 자바스크립트를 작성하려면 {} 안에 써주기 */}
{/* 방법 1과 같은 형식 다만 [] 안에 쓰는 것 */}
{/* {[1, 2, 3, 4, 5, 6, 7].map((el) => (
<span key={el} onClick={onClickPage} id={String(el)}>
{el}
</span>
))} */}
{/* 📌 방법 1 */}
{/* 만약 100 페이지까지 있으면 어떻게 할 것인가? 그리고 이것들을 하나하나 하드코딩 할 것인가? 아래들을 합칠 방법은? 다음 이전 페이지는? */}
{/* <span onClick={onClickPage} id="1">
1
</span>
<span onClick={onClickPage} id="2">
2
</span>
<span onClick={onClickPage} id="3">
3
</span> */}
</div>
);
}
import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import { useState } from "react";
// 게시글 몇번 보여줘가 아니라 전체 보여주니까 넘버 빼도 됨
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const MyRow = styled.div`
display: flex;
flex-direction: row;
`;
const MyColumn = styled.div`
/* width: 25%; */
`;
export default function MapBoardPage() {
const { data, refetch } = useQuery(FETCH_BOARDS);
// 기준을 시작페이지 '1'로 잡는다
const [startPage, setStartPage] = useState(1);
const onClickPage = (event) => {
refetch({ page: Number(event.target.id) });
};
// 이전페이지
// 기준 페이지에 - 10
const onClickPrevPage = () => {
// 기존에 - 10
setStartPage((prev) => prev - 10);
};
// 다음페이지
// 기준 페이지에 + 10
const onClickNextPage = () => {
// 기존에 + 10
setStartPage((prev) => prev + 10);
};
console.log(data);
return (
<div>
{data?.fetchBoards.map((el) => (
<MyRow key={el._id}>
<MyColumn>{el.writer}</MyColumn>
<MyColumn>{el.title}</MyColumn>
</MyRow>
))}
<span onClick={onClickPrevPage}>이전페이지</span>
{new Array(10).fill(1).map((_, index) => (
<span
key={index + startPage}
onClick={onClickPage}
id={String(index + startPage)}
>
{index + startPage}
</span>
))}
<span onClick={onClickNextPage}>다음페이지</span>
</div>
);
}
위 로직에는 문제가 있다. - page 가 나오고, 마지막 페이지도 끝없이 나온다. 즉 시작과 끝을 정해줘야한다.
import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import { useState } from "react";
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
// 게시글 총 개수 알기
// 객체로 받아오는 것이 아니라서 중괄호 필요 없음
const FETCH_BOARD_COUNT = gql`
query fetchBoardsCount {
fetchBoardsCount
}
`;
const MyRow = styled.div`
display: flex;
flex-direction: row;
`;
const MyColumn = styled.div`
/* width: 25%; */
`;
export default function MapBoardPage() {
const [startPage, setStartPage] = useState(1);
const { data, refetch } = useQuery(FETCH_BOARDS);
const { data: dataBoardsCount } = useQuery(FETCH_BOARD_COUNT);
// 마지막 페이지 구하기
const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);
const onClickPage = (event) => {
refetch({ page: Number(event.target.id) });
};
const onClickPrevPage = () => {
// 이전 페이지 버튼을 눌렀을 때 - 페이지로 못 넘어가게 하기
if (startPage === 1) return;
setStartPage((prev) => prev - 10);
refetch({ page: startPage - 10 });
};
// 1) lastPage 마지막 페이지 계산하기 ex) 35개 게시글이 있다면 한 페이지에 10개 나오니까 총 4페이지 (올림) 총 개수 나누기 10 하고 올림
// 총 개수는 백엔드에서 제공을 해줌 fetchBoardsCount 글 총 개수를 10으로 나누고 올림을 해주면 마지막 페이지 lastPage 가 된다.
// 2) startPage <= lastPage : 시작페이지는 마지막페이지보다 작거나 같아야 한다.
const onClickNextPage = () => {
// 전체 조건에 대해서 부정
if (!(startPage + 10 <= lastPage)) return;
setStartPage((prev) => prev + 10); // 기존 +10
refetch({ page: startPage + 10 });
};
return (
<div>
{data?.fetchBoards.map((el) => (
<MyRow key={el._id}>
<MyColumn>{el.writer}</MyColumn>
<MyColumn>{el.title}</MyColumn>
</MyRow>
))}
<span onClick={onClickPrevPage}>이전페이지</span>
{new Array(10).fill(1).map((_, index) =>
index + startPage <= lastPage ? (
<span
key={index + startPage}
onClick={onClickPage}
id={String(index + startPage)}
>
{` `}
{index + startPage}
</span>
) : (
<span></span>
)
)}
<span onClick={onClickNextPage}>다음페이지</span>
</div>
);
}
페이지네이션을 다른 페이지에서도 사용하려면 따로 컴포넌트로 빼서 import 하는 형식으로 사용하면 된다.
import { useQuery, gql } from "@apollo/client";
import Board from "../../src/components/units/board/14-05-board-pagination/Board";
import PageNation from "../../src/components/units/board/14-05-board-pagination/Pagination";
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const FETCH_BOARD_COUNT = gql`
query fetchBoardsCount {
fetchBoardsCount
}
`;
export default function MapBoardPage() {
const { data, refetch } = useQuery(FETCH_BOARDS);
const { data: dataBoardsCount } = useQuery(FETCH_BOARD_COUNT);
const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);
return (
<div>
<Board data={data} />
// 페이지네이션 import
// 코드의 길이가 짧아져서 가독성이 높아짐
<PageNation refetch={refetch} lastPage={lastPage} />
</div>
);
}
무한 스크롤은 사용자가 페이지 하단에 도달했을 때, 콘텐츠가 계속 로드되는 사용자 경험(UX, User EXperience) 방식이다.
이를 구현하기 위해 가장 많이 사용되는 react infinite scroller
를 사용해서 무한 스크롤을 구현해보자
https://www.npmjs.com/package/react-infinite-scroller
yarn add react-infinite-scroller
yarn add -D @types/react-infinite-scroller
{item}
이 보여질 데이터
hasMore
: 데이터 더 있는지 없는지 check
loadMore
: 데이터가 더 있으면 스크롤 내렸을 때 실행킬 함수
useWindow = {false}
지정하고, 스크롤을 감싸는 태그에 style로 높이를 설정하고 overflow:auto
를 준다. 그럼 지정한 높이보다 데이터의 길이가 크면 해당 박스에 스크롤이 작동한다. 위 두개가 빠지면 전체 바디 스크롤이 작동한다.
import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import InfiniteScroll from "react-infinite-scroller";
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const MyRow = styled.div`
display: flex;
flex-direction: row;
`;
const MyColumn = styled.div`
width: 25%;
`;
export default function MapBoardPage() {
const { data, fetchMore } = useQuery(FETCH_BOARDS); // fetchMore 추가적으로 10개를 더 페치한다
// 📌 무한스크롤 함수
const onLoadMore = () => {
if (!data) return; // 만약에 데이터가 없다면 스크롤 실행 안됨
// Q. refetch 와 fetchmore 차이는 ?
// refetch : 기존에 했던 것을 다시 패치하는 것
// fetchmore: 기존에 n개가 있었는데 추가로 더 fetch를 한다.
fetchMore({
variables: {
// 현재까지 받은 페이지 fetchBoards 의 수 구하기
// 기존에 받았던 데이터 갯수의 길이 / 10 = 현재까지 받은 페이지 수
// + 1 = 다음페이지
page: Math.ceil(data.fetchBoards.length / 10) + 1,
},
// updateQuery: 기존에 저장되어있던 쿼리를 업데이트
// prev: 이전 데이터 1 ~ 10 (기존 페이지)
// fetchMoreResult: 11 ~ 20 번 (추가로 요청한 페이지)
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult.fetchBoards)
return { fetchBoards: [...prev.fetchBoards] }; // 다음 표출될 데이터 없을 경우 기존 페치보드만 넣어주기
return {
fetchBoards: [...prev.fetchBoards, ...fetchMoreResult.fetchBoards], // 기존의 10개를 뿌리고, 추가로 받은 10개를 뿌리기 (이전 데이터와 다음 데이터 함께 표출 > 스프레드 연산자)
};
},
});
};
return (
<InfiniteScroll pageStart={0} loadMore={onLoadMore} hasMore={true}>
{data?.fetchBoards.map((el: any) => (
<MyRow key={el._id}>
<MyColumn>{el._id}</MyColumn>
<MyColumn>{el.writer}</MyColumn>
<MyColumn>{el.title}</MyColumn>
</MyRow>
))}
</InfiniteScroll>
);
}