Pagination (페이지네이션)
페이지네이션은 화면에 나타나는 데이터를 여러 개의 페이지별로 나눠서 보여주는 UI다.
검색결과 하단이나, 쇼핑몰 하단에서 쉽게 접할 수 있다. 보통 숫자를 선택하거나, 원하는 범위를 직접 입력하는 방식으로 구현되어 있다.
비슷한 UI로는 Infinite Scroll(무한 스크롤)이 있다.이는 페이지를 이동해서 보여주는 방식이 아닌, 한 화면에서 데이터를 아래로 끊임없이 나타나게 하는 UI다.
각각의 장단점이 있어서 기획이나 상황, 사용자의 요구에 맞게 잘 선택해서 사용해야 한다.
페이지네이션
- 장점
- 많은 양의 데이터를 쉽게 탐색하고, 원하는 섹션에 빠르게 도달할 수 있다.
- 데이터를 나눠서 보여주기 때문에, 로딩 시간이 느린 사이트에 유용하게 사용된다.
- 사용자 입장에서 컨텐츠 내에 현재 어디에 있는지 명확하게 알 수 있기 때문에, 사이트 구조를 이해하는데 쉽다.
- 단점
- 원하는 컨텐츠를 찾기 위해 많은 수의 페이지를 클릭해야 할 수도 있다.
- 많은 양의 컨텐츠에 대한 페이지네이션 구현, 유지 관리, 업데이트 등에서 시간이 많이 걸릴 수 있다.
무한 스크롤
- 장점
- 페이지를 벗어나지 않고, 한 페이지에서 끊김 없는 브라우징이 가능하다.
- 사용자가 관심 있는 컨텐츠를 계속 스크롤할 수 있기 때문에, 사용자 참여를 높일 수 있다.
- 단점
- 각 섹션 간에 명확한 경계가 없기 때문에, 특정 컨텐츠를 찾기 어려울 수 있다.
- 계속 많은 컨텐츠가 페이지에 로드되게 때문에, 로딩 시간이 느려질 수 있다.
- 새로운 컨텐츠에 도달하려면 계속 스크롤을 해야 하므로, 인터넷 연결이 느린 사용자들에게는 문제가 생길 수 있다.
구현하기
페이지네이션을 구현하는 방법은 아주 다양하다. 이 글에서는 얼마 전 만들었던 내 방법대로 구현을 할 것이고, Custom Hook으로 여러 다른 프로젝트에서도 사용이 가능하도록 만들 것이다.
처음 페이지네이션을 구현하려 했을 때 떠오르는 방법이 있었고, 그 방법 그대로 구현이 되었기 때문에 추가로 수정은 하지 않았다.
유틸함수
먼저, 페이지네이션 하단에 있는 숫자들을 그룹화하는 작업이 필요하다.
페이지가 1~20까지 있다고 가정을 했을 때, 하단에 노출되는 숫자를 정하기 위한 작업이다.
위 사진에서는 20개의 페이지를 한 화면에 5개씩 나누어서 노출한다.
모든 페이지를 한 곳에 담아서 slice 하는 방법도 있겠지만, 개인적인 편의를 위해 노출되는 페이지별로 그룹을 지었다.
화살표를 눌러서 페이지를 건너뛰고, 다른 목록으로 넘어가면 다음 그룹을 보여주는 방식이다.
[ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20] ]
예시 코드에서 페이지의 개수는 5개로 정해져 있는데, 사용하는 상황마다 노출되는 페이지가 다르기 때문에 변경이 가능하도록 유틸함수를 작성했다.
const formatPageArray = (
totalCount: number, // 전체 item의 갯수
pageGroupCount: number, // 한 페이지에 보여질 페이지 번호의 갯수
productCount: number // 한 페이지에 보여질 item의 갯수
) => {
const pageNumbers = [];
const totalPage = Math.ceil(totalCount / productCount);
for (let i = 1; i <= totalPage; i += pageGroupCount) {
const pageGroup = [];
for (let j = i; j < i + pageGroupCount && j <= totalPage; j++) {
pageGroup.push(j);
}
pageNumbers.push(pageGroup);
}
return pageNumbers;
};
함수는 3가지의 parameter를 받아서 이중배열 구조를 반환한다.
totalCount는 보통 api 요청을 하면 함께 전달해 준다.
모든 데이터를 한 번에 받지 않고 페이지 별로 받기 때문에, 별도의 값으로 같이 보내주는 경우가 일반적이다.
나머지 2개의 parameter는 기획에 맞춰서 설정해 주면 된다.
Custom Hook
훅에서도 마찬가지로 3개의 parameter를 받는다.
페이지가 변할 때마다 전체 그룹을 제외하고 반환하는 모든 값이 달라질 수 있다.
여기서는 유틸함수를 따로 분리했지만, 훅에 포함시켜서 사용해도 상관은 없다.
page parameter는 현재 string으로 작성되어 있지만, 구현된 상태에 맞게 number로 수정할 수도 있다.
import { useMemo } from 'react';
import { formatPageArray } from 'utilities';
const usePagination = (page: string, totalCount: number, pageGroupCount: number) => {
// 이중배열로 구성된 전체 페이지 그룹을 반환
const pageGroups = useMemo(
() => formatPageArray(totalCount, pageGroupCount),
[totalCount, pageGroupCount]
);
// 현재 페이지가 속한 그룹을 반환
const currentPageGroup = useMemo(
() => pageGroups[pageGroups.findIndex((pageGroup) => pageGroup.includes(+page))],
[pageGroups, page]
);
// 이전 그룹의 마지막 페이지를 반환
const prevPageGroup = useMemo(
() => Math.floor((+page - 1) / pageGroupCount) * pageGroupCount,
[page, pageGroupCount]
);
// 다음 그룹의 첫 페이지를 반환
const nextPageGroup = useMemo(
() => Math.ceil(+page / pageGroupCount) * pageGroupCount + 1,
[page, pageGroupCount]
);
// 이전 그룹의 유무를 반환
const isFirst = useMemo(() => currentPageGroup[0] === 1, [currentPageGroup]);
// 다음 그룹의 유무를 반환
const isLast = useMemo(
() => currentPageGroup[0] === pageGroups[pageGroups.length - 1][0],
[currentPageGroup, pageGroups]
);
return { currentPageGroup, prevPageGroup, nextPageGroup, isFirst, isLast };
};
export default usePagination;
사용하기
페이지를 표시하는 디자인이나, Component 등을 제외하면 사용할 준비가 끝났다.
기능적인 부분을 구현하는 글이기 때문에 사용하는 Component의 작성은 글에서 제외했다.
사용할 Component에서 필요한 parameter를 넣고 반환값을 적절히 사용해 주면 된다.
아래 Pagination Component는 위에서 예시로 들었던 사진을 별도의 Component로 만들어놓은 코드이다.
const Pagination = ({ page, totalCount, pageGroupCount }: PaginationProps) => {
const { currentPageGroup, prevPageGroup, nextPageGroup, isFirst, isLast } = usePagination(
page,
totalCount,
pageGroupCount
);
return (
<Container>
<Link href={`/page/${prevPageGroup}`}>
<Button disabled={isFirst}>
<PrevIcon />
</Button>
</Link>
<PageWrapper>
{currentPageGroup.map((pageNumber) => (
<Link key={pageNumber} href={`/page/${pageNumber}`}>
<Page selected={pageNumber === +page} disabled={pageNumber === +page}>
{pageNumber}
</Page>
</Link>
))}
</PageWrapper>
<Link href={`/page/${nextPageGroup}`}>
<Button disabled={isLast}>
<NextIcon />
</Button>
</Link>
</Container>
);
};
export default Pagination;
완성
'개발' 카테고리의 다른 글
Next - Image (nextjs 13) (0) | 2023.03.07 |
---|---|
React - Image Lazy Loading 구현하기 (0) | 2023.02.10 |
npm - Custom hook Publish ! (0) | 2023.02.08 |
React - Effect Hook ( Clean-up ) (0) | 2023.01.27 |