개발인생/Project

페이징처리 프로젝트 구현(React)

forri 2025. 3. 17. 17:54

Pagination.js

// Pagination.js
import React from 'react';

// Pagination 컴포넌트는 여러 props를 받아 페이지네이션과 관련된 다양한 기능을 제공합니다.
function Pagination({
    currentPage,            // 현재 페이지 번호
    totalPages,             // 전체 페이지 수
    itemsPerPage,           // 한 페이지당 표시할 항목 수
    totalItems,             // 전체 항목 수
    isLoading,              // 데이터 로딩 상태
    pageInputValue,         // 직접 입력한 페이지 번호의 값
    handlePage,             // 페이지 이동 시 호출되는 핸들러 함수
    handleItemsPerPageChange, // 페이지당 항목 수 변경 시 호출되는 핸들러
    handlePageInputChange,  // 페이지 번호 입력 시 호출되는 핸들러
    handleDeleteSelected,   // 선택된 항목 삭제를 위한 핸들러 (선택 삭제 기능)
    selectedItems,          // 현재 선택된 항목 배열
    showFilters = true,     // 필터 영역을 보여줄지 여부 결정하는 옵션 (기본값 true)
}) {
    return (
        // 전체 컨테이너: showFilters가 false인 경우, 페이지네이션 영역을 가운데 정렬함
        <div
            className="pagination-container"
            style={{ justifyContent: !showFilters ? 'space-around' : 'space-between' }}
        >

            {/* 좌측 영역: 페이지당 항목 수 선택 및 선택 삭제 버튼 (showFilters가 true일 때만 표시) */}
            {
                showFilters && (
                    <div className="pagination-sub left">
                        {/* 선택된 항목이 있을 경우에만 선택 삭제 버튼을 표시 */}
                        {Array.isArray(selectedItems) && selectedItems.length > 0 && (
                            <button className="box mr10 color_border red" onClick={handleDeleteSelected}>
                                <i className="bi bi-trash3"></i>{selectedItems.length}건 삭제
                            </button>
                        )}
                        {/* 한 페이지당 표시할 항목 수를 입력받는 인풋 */}
                        <input
                            type="number"
                            id="itemsPerPage"
                            className="box"
                            value={itemsPerPage}
                            onChange={handleItemsPerPageChange}
                            min={1}
                            max={100}
                            step={1}
                        />
                        {/* 인풋 옆에 현재 항목 수 및 전체 항목 수 표시 */}
                        <label htmlFor="itemsPerPage">
                            건씩 보기 / <b>{isLoading ? '-' : totalItems}</b>건
                        </label>
                    </div>
                )
            }

            {/* 중앙 영역: 페이지네이션 버튼 */}
            <div className="pagination">
                {/* '처음' 버튼: 현재 페이지가 1보다 클 경우에만 표시 */}
                {currentPage > 1 && (
                    <button className="box icon first" onClick={() => handlePage(1)}>
                        <i className="bi bi-chevron-double-left"></i>
                    </button>
                )}

                {/* '이전' 버튼: 현재 페이지가 1보다 클 경우에만 표시 */}
                {currentPage > 1 && (
                    <button className="box icon left" onClick={() => handlePage(currentPage - 1)}>
                        <i className="bi bi-chevron-left"></i>
                    </button>
                )}

                {/* 페이지 번호 블록: 최대 5개의 페이지 번호를 동적으로 생성 */}
                {Array.from({ length: Math.min(5, totalPages) }, (_, index) => {
                    // 현재 페이지 그룹의 시작 번호를 계산 (예: 1~5, 6~10 등)
                    const startPage = Math.max(Math.floor((currentPage - 1) / 5) * 5 + 1, 1);
                    const page = startPage + index;
                    return (
                        page <= totalPages && (
                            <button
                                key={page}
                                onClick={() => handlePage(page)}
                                className={currentPage === page ? 'box active' : 'box'}
                            >
                                {page}
                            </button>
                        )
                    );
                })}

                {/* '다음' 버튼: 현재 페이지가 전체 페이지 수보다 작을 경우에만 표시 */}
                {currentPage < totalPages && (
                    <button className="box icon right" onClick={() => handlePage(currentPage + 1)}>
                        <i className="bi bi-chevron-right"></i>
                    </button>
                )}

                {/* '끝' 버튼: 현재 페이지가 전체 페이지 수보다 작을 경우에만 표시 */}
                {currentPage < totalPages && (
                    <button className="box icon last" onClick={() => handlePage(totalPages)}>
                        <i className="bi bi-chevron-double-right"></i>
                    </button>
                )}
            </div>

            {/* 우측 영역: 페이지 번호 직접 입력 (showFilters가 true일 때만 표시) */}
            {
                showFilters && (
                    <div className="pagination-sub right">
                        {/* 직접 페이지 번호를 입력할 수 있는 인풋 */}
                        <input
                            type="number"
                            id="pageInput"
                            className="box"
                            value={pageInputValue}
                            onChange={handlePageInputChange}
                            min={1}
                            max={totalPages}
                            step={1}
                        />
                        <label htmlFor="pageInput">/ <b>{totalPages}</b>페이지</label>
                    </div>
                )
            }

        </div >
    );
}

export default Pagination;

동작 원리

  • 컨테이너 스타일: showFilters 값에 따라 좌측(필터/항목 수, 선택 삭제)와 우측(페이지 번호 입력) 영역의 정렬 방식 조정
  • 선택 삭제 기능: selectedItems 배열의 길이에 따라 선택 삭제 버튼이 표시되며 클릭 시 handleDeleteSelected 함수 호출
  • 항목 수 선택: 사용자가 페이지당 몇 개의 항목을 볼지를 선택할 수 있도록 숫자 인풋을 제공하며, 변경 시 handleItemsPerPageChange 함수 호출
  • 페이지네이션 버튼: 현재 페이지와 전체 페이지 수를 기반으로 '처음', '이전', 개별 페이지 번호, '다음', '끝' 버튼을 조건부로 렌더링함. 사용자가 버튼을 클릭하면 handlePage 함수가 호출되어 해당 페이지로 이동
  • 페이지 입력: 직접 페이지 번호를 입력할 수 있는 인풋을 제공해 변경 시 handlePageInputChange 함수 호출

 

이거 임포트해서 쓰면 좋았을텐데, 프로젝트 당시 시간도 없고 급한 마음에 아래 처럼 단순화 해서 처리 시켰다 🥲

 

 


페이지네이션 부분 단순화

// 단순화된 페이지네이션 영역
<div className="pagination">
    {/* 페이지 번호가 1보다 큰 경우 '처음'과 '이전' 버튼 표시 */}
    {page > 1 && (
        <>
            <button className="box icon first" onClick={() => PageChange(1)}>
                <i className="bi bi-chevron-double-left"></i>
            </button>
            <button className="box icon left" onClick={() => PageChange(page - 1)}>
                <i className="bi bi-chevron-left"></i>
            </button>
        </>
    )}
    
    {/* 최대 5개의 페이지 번호 버튼을 생성 */}
    {Array.from({ length: Math.min(5, totalPages) }, (_, index) => {
        // 현재 페이지 그룹의 시작 번호 계산 (예: 1~5, 6~10 등)
        const startPage = Math.floor((page - 1) / 5) * 5 + 1;
        const currentPage = startPage + index;
        return (
            currentPage <= totalPages && (
                <button
                    key={currentPage}
                    onClick={() => PageChange(currentPage)}
                    className={currentPage === page ? 'box active' : 'box'}
                >
                    {currentPage}
                </button>
            )
        );
    })}
    
    {/* 페이지 번호가 전체 페이지 수보다 작은 경우 '다음'과 '끝' 버튼 표시 */}
    {page < totalPages && (
        <>
            <button className="box icon right" onClick={() => PageChange(page + 1)}>
                <i className="bi bi-chevron-right"></i>
            </button>
            <button className="box icon last" onClick={() => PageChange(totalPages)}>
                <i className="bi bi-chevron-double-right"></i>
            </button>
        </>
    )}
</div>
<div className="pagination-sub right"></div>

동작 원리

  • 조건부 렌더링:
    • page가 1보다 클 때 '처음'과 '이전' 버튼을 묶어서 표시
    • page가 전체 페이지 수보다 작을 때, '다음'과 '끝' 버튼을 묶어서 표시
  • 페이지 번호 버튼 생성:
    • Array.from을 사용하여 최대 5개의 페이지 번호를 생성하며
    • 현재 페이지 그룹(예를 들어, 15, 610 등)의 시작 번호를 계산하여 버튼을 렌더링
    • 각 버튼 클릭 시 PageChange 함수가 호출되어 해당 페이지로 이동
  • 추가 영역: 우측의 pagination-sub right 영역은 이 코드에서는 빈 영역으로 남겨 추가적인 기능(예: 페이지 직접 입력) 등을 넣을 여지를 제공

App.js에서 Pagination 사용하려면?

👉 import 후에 JSX 안에서 필요한 props를 전달하며 컴포넌트를 사용

import Pagination from './main/react/components/common/Pagination';

function App() {
  const currentPage = 1;
  const totalPages = 10;
  // 기타 필요한 상태와 핸들러 선언

  return (
    <div>
      <Pagination
        currentPage={currentPage}
        totalPages={totalPages}
        itemsPerPage={10}
        totalItems={100}
        isLoading={false}
        pageInputValue={currentPage}
        handlePage={(page) => console.log('페이지 변경:', page)}
        handleItemsPerPageChange={(e) => console.log('페이지당 항목 수 변경:', e.target.value)}
        handlePageInputChange={(e) => console.log('페이지 입력 변경:', e.target.value)}
        handleDeleteSelected={() => console.log('선택된 항목 삭제')}
        selectedItems={[]}
        showFilters={true}
      />
    </div>
  );
}

export default App;

지금보면 임포트해서 쓰는 간단한건데 프로젝트 할때는 막무가내로 단순화 시켜서 했었다. 면접 준비 하다가 아쉬운 마음이 들어 포스팅해본다.