너와 나의 프로그래밍
React.js - React Pagination 본문
Pagination 기능은 정말 프론트엔드 개발에 있어서 피할 수 없는 개발중에 하나라고 생각한다.
항상 개발을 할 때 마다 페이징 처리가 은근히 어렵다는걸 항상 느끼는데 구글링을 해봐도 각자 방식이 너무 다를뿐더러 너무 코드도 뒤죽박죽이라 최대한 깔끔하게 최대한 쉽게 구현을 해봤다.
개발 환경은 React.js와 Typescript를 기본적인 디자인은 Styled Component를 사용했다.
조건 🤔
다양한 페이지네이션의 조건들이 있지만 이번에 구현해본 조건은 아래와 같다.
- 한 페이지 안에 10개씩 보여주기
- 페이지의 번호는 5개씩 보여주기
- 페이지 번호를 눌렀을 때 해당 페이지를 보여주기
- 페이지 번호의 양 옆에 뒤로 건너뀌기, 뒤로 가기 / 다음 페이지로 가기, 앞으로 건너뛰기
- 첫 시작과 맨 끝으로 갔을 때 버튼 disabled 처리
구현 😀
1. usePagination.ts(js)
import { useState } from "react";
export const usePagination = () => {
const [cntPage, setCntPage] = useState<number>(10);
const [totalCount, setTotalCount] = useState<number>(0);
const [page, setPage] = useState<number>(1);
const setPagination = (
cntPage: number,
totalCount: number,
page: number
) => {
setCntPage(cntPage);
setTotalCount(totalCount);
setPage(page);
};
return { cntPage, totalCount, page, setPagination };
};
먼저 usePagination이라는 Custom Hook을 하나 생성해 준다. 나중에 Pagination의 페이지가 바뀔 때 마다 페이징의 대한 상태값을 유지해줘야 하는데 있어 필수적이다.
먼저 usePagination hook안에 state들을 보면 'cntPage'는 한 페이지에서 보여줄 갯수, 'totalCount'는 API를 호출했을 때 총 Element들의 갯수, 'page'는 해당 페이지의 번호다.
'setPagination' function은 실제로 페이징을 하면서 위 상태값들을 변경해주는 함수다.
2. Pagination.tsx(jsx)
import styled from "styled-components";
import { IPagination } from "./types";
import { useState, useCallback, useMemo } from "react";
const Container = styled.div`
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 30rem;
margin-top: 3.2rem;
`;
const Button = styled.button`
cursor: pointer;
background-color: #ffffff;
&:disabled {
color: #e2e2ea;
cursor: default;
}
`;
const PageWrapper = styled.div`
display: flex;
margin: 0 16px;
`;
const Page = styled.button<{ selected: boolean }>`
width: 2.8rem;
height: 2.8rem;
font-size: 1.2rem;
line-height: 1.6rem;
text-align: center;
letter-spacing: 0.01rem;
border-radius: 100%;
background-color: ${({ selected }) =>
selected ? "#363740" : "transparent"};
color: ${({ selected }) => (selected ? "#fff" : "#666666")};
cursor: pointer;
& + & {
margin-left: 4px;
}
&:disabled {
cursor: default;
}
`;
const Pagination = ({ pagination }: IPagination) => {
const [pageState, setPageState] = useState<number>(1);
const { page, cntPage, setPagination, totalCount } = pagination;
const maxPage = Math.ceil(totalCount / cntPage);
const pageNumberList = useMemo(() => {
let pageNumbers = [];
for (let i = pageState; i < pageState + 5 && i <= maxPage; i++) {
pageNumbers.push(i);
}
return pageNumbers;
}, [pageState]);
const selectPageNum = useCallback((value: number) => {
setPagination(cntPage, totalCount, value);
}, []);
const goToPage = useCallback(
(move: string) => {
setPagination(
cntPage,
totalCount,
move === "next" ? page + 1 : page - 1
);
page % 5 === 0
? setPageState((prev) => prev + 5)
: setPageState((prev) => prev - 5);
},
[pageState, page]
);
const goToJumpPage = useCallback(
(move: string) => {
setPagination(
cntPage,
totalCount,
move === "next" ? page + 5 : page - 5
);
if (page % 5 === 0 && move === "next") {
setPageState((prev) => prev + 5);
}
if (page % 5 === 1 && move === "prev") {
setPageState((prev) => prev - 5);
}
},
[pageState]
);
return (
<Container>
<Button
disabled={pageState - 5 < 1}
onClick={() => goToJumpPage("prev")}
>
{"<<"}
</Button>
<Button disabled={page === 1} onClick={() => goToPage("prev")}>
{"<"}
</Button>
<PageWrapper>
{pageNumberList.map((value) => (
<Page
key={value}
selected={value === pagination.page}
onClick={() => selectPageNum(value)}
disabled={value === pagination.page}
>
{value}
</Page>
))}
</PageWrapper>
<Button disabled={page === maxPage} onClick={() => goToPage("next")}>
{">"}
</Button>
<Button
disabled={pageNumberList.length < 5}
onClick={() => goToJumpPage("next")}
>
{">>"}
</Button>
</Container>
);
};
export default Pagination;
설명
const [pageState, setPageState] = useState<number>(1);
const { page, cntPage, setPagination, totalCount } = pagination;
const maxPage = Math.ceil(totalCount / cntPage);
먼저 'pageState' 상태값을 선언해준다. 초기값을 1로 지정한 이유는 Pagination을 사용하고 있는 페이지의 첫 렌더링을 할 때 항상 1로 지정해 줘야 넘버링이 1번부터 시작하기 때문이다.
그리고 실제 페이징처리를 위한 Custom Hook으로 만들어준 'usePagination' Hook을 선언해준다.
'maxPage'는 API로 불러온 전체 Element의 갯수 / 현재 보여줄 Element의 갯수를 계산해서 나온 결과 값으로 넘버링의 대한 계산을 위해 필요하다.
const pageNumberList = useMemo(() => {
let pageNumbers = [];
for (let i = pageState; i < pageState + 5 && i <= maxPage; i++) {
pageNumbers.push(i);
}
return pageNumbers;
}, [pageState]);
'pageNumberList'는 'maxPage' 값으로 나온 결과값을 토대로 실제 페이지 넘버링을 하기 위한 함수다.
'pageState'를 통해 초기값 1부터 maxPage의 length까지 쭉~ 넘버 리스트를 생성해 준다.
그리고 pageState가 바뀔 때 마다 새로 넘버링 리스트를 재생성해 준다.
const selectPageNum = useCallback((value: number) => {
setPagination(cntPage, totalCount, value);
}, []);
{pageNumberList.map((value) => (
<Page
key={value}
selected={value === pagination.page}
onClick={() => selectPageNum(value)}
disabled={value === pagination.page}
>
{value}
</Page>
))}
'selectPageNum' function은 페이지 넘버링으로 보여주는 페이지 번호를 눌렀을 때 발생하는 함수다.
'pageNumberList'를 통해 넘버 리스트를 생성해 주고 반복문을 돌려서 페이지 번호에 대한 렌더링을 해준다.
onClick에 selectPageNum 함수를 바인딩 해줌으로 click 했을 때 value Paramater를 넘겨서 함수를 실행해 주도록 한다.
selectPageNum 함수는 click 이벤트가 발생할 때 Parameter로 넘겼던 value(페이지 번호)를 함수 안 custom hook으로 만든 setPagination 함수안 page을 value로 세팅하면서 해당 페이지를 눌렀을 때 page의 번호를 바꿔가며 페이징 처리를 해준다.
const goToPage = useCallback(
(move: string) => {
setPagination(
cntPage,
totalCount,
move === "next" ? page + 1 : page - 1
);
if (page % 5 === 0 && move === "next") {
setPageState((prev) => prev + 5);
}
if (page % 5 === 1 && move === "prev") {
setPageState((prev) => prev - 5);
}
},
[pageState, page]
);
const goToJumpPage = useCallback(
(move: string) => {
setPagination(
cntPage,
totalCount,
move === "next" ? page + 5 : page - 5
);
setPageState((prev) => (move === "next" ? (prev += 5) : (prev -= 5)));
},
[pageState]
);
goToPage는 이전, 다음 버튼의 대한 이벤트 처리다.
move Parameter를 하나 만들고 다음/이전을 구분했고 page가 1씩 증감하는 기능을 구현했다.
page % 5 === 0라는 분기처리를 한 이유는 page가 5일 때 다음 버튼을 누르면 pageState가 6으로 6에서 이전버튼을 누르면 1로 넘버링을 바꾸기 위해서다.
goToJumpPage는 건너뛰기 버튼에 대한 이벤트 처리다.
move Parameter를 하나 만들고 건너뛰기를 했으며 page를 5씩 증감하는 기능을 구현했다.
마찬가지로 넘버링을 다시 하기 위해 같이 넘버링을 5씩 증감하는 setPageState를 해줬다.
Styled Component를 사용해서 간단한 페이지네이션의 대한 스타일링을 했고 위에서 소개한 3, 4, 5번의 대한 조건 처리를 하였다.
후기 😊
페이지네이션의 대한 처리는 참 말로는 간단하면서도 실제 구현을 하려고 하면 굉장히 까다로운 조건들이 많아서 일일히 하나하나 신경쓰기가 참 힘들기도 하다. 그리고 원하는 페이징 처리도 너무 많아서 뭐가 정답이라고 하기에도 참 애매하다.
하지만 위 코드를 통해서 여러가지 활용도가 매우 높다고 생각한다. 기본적인 페이지네이션의 대한 이벤트 처리는 저 안에서 코드를 넣고 빼면서 전부 가능하다.
실제 저 코드는 API를 호출하고, 테이블에 넣으면서 직접 보는 것이 제일 좋다고 생각한다. 블로그의 글이 너무 길어지면 읽는데 피로할 수 있으니 실제 구현해 놓은 코드를 github 링크를 통해 확인했으면 좋겠다!
링크 : https://github.com/soft91/React-Study/tree/master/react-pagination
'Front-End > Next.js & React.js' 카테고리의 다른 글
Next.js - NestedLayout 사용 방법 (Pages Router) (0) | 2024.02.05 |
---|---|
React.js - 엑셀 다운로드(Blob Type 다운로드) (0) | 2023.05.07 |
Next.js - Link와 Router의 다른 점과 getServerSideProps (0) | 2023.03.12 |
Next.js - SSR Cookie Setting (0) | 2023.02.12 |
React.js - Button onClick으로 부모의 onChangeHandler 이벤트 발생 시키기 (0) | 2023.01.24 |