페이지네이션(Pagination) 구현하기2: 사용자 경험 향상을 위한 기능 추가 (Next.Js-Typescript)

Devinix·2023년 10월 24일
0
post-thumbnail

개요

pagination은 웹사이트의 사용성을 크게 향상시킬 수 있는 중요한 기능이다. 사용자가 수많은 데이터 중에서 원하는 정보를 쉽게 찾을 수 있도록 도와주며, 데이터 로딩 시간을 단축시켜 서비스의 퍼포먼스를 개선할 수 있다. 이번 글에서는, 저번 글에서 다룬 모양새만 구현했던 페이지네이션 컴포넌트에 여러 가지 새로운 기능을 추가하여 사용자 경험을 더욱 향상시킨 점에 대해 소개하려고 한다.

저번 블로그: https://velog.io/@dpldpl/%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Pagination-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%ED%95%9C-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%ED%8A%B9%EC%A0%95-%EC%88%98%EC%9D%98-%ED%95%AD%EB%AA%A9%EB%A7%8C-%ED%91%9C%EC%8B%9C-Next.Js-Typescript

기존의 코드

"use client";

import Link from "next/link";
import styles from "./Pagination.module.scss";
import React from "react";

interface IPaginationProps {
  currentPage: number;
  totalContents: number;
  paginate: (pageNumber: number) => void;
  contentsPerPage: number;
}

function Pagination({
  currentPage,
  totalContents,
  paginate,
  contentsPerPage,
}: IPaginationProps): JSX.Element {
  const pageNumbers: number[] = [];

  for (let i = 1; i <= Math.ceil(totalContents / contentsPerPage); i++) {
    pageNumbers.push(i);
  }

  return (
    <>
      <nav className={styles.container}>
        <ul className={styles.pagination}>
          {pageNumbers.map((number) => (
            <li
              key={number}
              className={`${styles.pageList} ${
                currentPage === number ? styles.active : ""
              }`}
            >
              <Link
                href="/"
                onClick={() => paginate(number)}
                className={styles.pageLink}
              >
                {number}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
    </>
  );
}

export default Pagination;

수정된 코드

"use client";

import Link from "next/link";
import styles from "./Pagination.module.scss";
import React, { useState } from "react";

interface IProps {
  currentPage: number;
  totalContents: number;
  paginate: (pageNumber: number) => void;
  contentsPerPage: number;
}

function Pagination({ totalContents, contentsPerPage, paginate }: IProps) {
  const [currentPage, setCurrentPage] = useState<number>(1);
  const pageNumbers: number[] = [];
  const maxPageNum = Math.ceil(totalContents / contentsPerPage);

  for (let i = 1; i <= maxPageNum; i++) {
    pageNumbers.push(i);
  }

  const updatePageNumbers = (page: number) => {
    let startPage = page - 4 > 1 ? page - 4 : 1;
    let endPage = startPage + 9 > maxPageNum ? maxPageNum : startPage + 9;

    while (endPage - startPage < 9 && startPage > 1) {
      startPage--;
    }

    return pageNumbers.slice(startPage - 1, endPage);
  };

  const [, setCurrentNumbers] = useState(updatePageNumbers(1));

  const visiblePageNumbers = updatePageNumbers(currentPage);

  const goToFirstPage = () => {
    setCurrentPage(1);
    paginate(1);
  };

  const goToLastPage = () => {
    setCurrentPage(maxPageNum);
    paginate(maxPageNum);
  };

  const handleClick = (number: number) => {
    setCurrentPage(number);
    setCurrentNumbers(updatePageNumbers(number));
    paginate(number);
  };

  return (
    <>
      <nav className={styles.container}>
        <ul className={styles.pagination}>
          <li>
            <Link
              href="#"
              onClick={() => goToFirstPage()}
              className={`${styles.pageLink} ${
                currentPage === 1 ? styles.disabled : null
              }`}
            >
              {"<<"}
            </Link>
          </li>
          <li>
            <Link
              href="#"
              onClick={() => handleClick(currentPage - 1)}
              className={`${styles.pageLink} ${
                currentPage === 1 ? styles.disabled : null
              }`}
            >
              {"<"}
            </Link>
          </li>
          {visiblePageNumbers.map((number) => {
            const pageListClass = `${styles.pageList} ${
              currentPage === number ? styles.active : null
            }`;
            return (
              <>
                <li key={number} className={pageListClass}>
                  <Link href="#" onClick={() => handleClick(number)}>
                    {number}
                  </Link>
                </li>
              </>
            );
          })}
          <li>
            <Link
              href="#"
              onClick={() => handleClick(currentPage + 1)}
              className={`${styles.pageLink} ${
                currentPage === maxPageNum ? styles.disabled : null
              }`}
            >
              {">"}
            </Link>
          </li>
          <li>
            <Link
              href="#"
              onClick={() => goToLastPage()}
              className={`${styles.pageLink} ${
                currentPage === maxPageNum ? styles.disabled : null
              }`}
            >
              {">>"}
            </Link>
          </li>
        </ul>
      </nav>
    </>
  );
}

export default Pagination;

개선 사항

현재 페이지 상태 추적:

useState를 사용하여 현재 페이지의 상태를 저장하고 업데이트할 수 있도록 하였다. 이를 통해 어떤 페이지가 현재 선택되어 있는지 사용자에게 명확하게 보여줄 수 있다.

const [currentPage, setCurrentPage] = useState<number>(1);

코드 설명
React의 useState를 사용하여 현재 페이지 번호를 추적한다. 이 상태는 사용자가 페이지 번호를 클릭할 때 업데이트되며, UI에 현재 활성화된 페이지를 반영한다.

총 페이지 수 계산:

전체 콘텐츠 수와 한 페이지에 표시할 콘텐츠 수를 기반으로 총 페이지 수를 계산한다. 이 정보는 사용자가 마지막 페이지에 도달했는지 여부를 판단하는 데 사용된다.

const maxPageNum = Math.ceil(totalContents / contentsPerPage);

코드 설명
전체 콘텐츠 수를 페이지 당 콘텐츠 수로 나누어 총 페이지 수를 계산한다. 이렇게 하면 페이지네이션이 얼마나 많은 페이지를 표시해야 하는지 알 수 있다.

동적 페이지 번호 업데이트:

현재 페이지 번호를 기준으로 페이지네이션 컨트롤에 표시할 페이지 번호의 범위를 동적으로 업데이트한다. 이는 사용자가 현재 선택한 페이지 주변의 페이지 번호만 볼 수 있도록 해서, 너무 많은 페이지 번호가 한 번에 표시되는 것을 방지한다.

  const updatePageNumbers = (page: number) => {
    let startPage = page - 4 > 1 ? page - 4 : 1;
    let endPage = startPage + 9 > maxPageNum ? maxPageNum : startPage + 9;

    while (endPage - startPage < 9 && startPage > 1) {
      startPage--;
    }

    return pageNumbers.slice(startPage - 1, endPage);
  };
  const [, setCurrentNumbers] = useState(updatePageNumbers(1));
  const visiblePageNumbers = updatePageNumbers(currentPage);

코드 설명
updatePageNumbers 함수는 현재 페이지 번호를 기준으로 페이지네이션 컨트롤에 표시할 페이지 번호의 범위를 동적으로 업데이트한다. 이렇게 하면, 사용자에게 현재 선택한 페이지 주변의 페이지 번호 10개만 보이게 되어 네비게이션이 용이해진다.

페이지 선택 시 동적 업데이트:

사용자가 페이지 번호를 클릭할 때마다, 페이지네이션 컨트롤이 동적으로 업데이트되어 현재 선택된 페이지를 반영한다. 이를 통해 사용자는 항상 어떤 페이지들이 이용 가능한지 명확하게 인지할 수 있다.

const handleClick = (number: number) => {
  setCurrentPage(number);
  setCurrentNumbers(updatePageNumbers(number));
  paginate(number);
};

코드 설명
handleClick 함수는 사용자가 페이지 번호를 클릭할 때 호출되며, 적절한 페이지 번호를 setCurrentPage와 setCurrentNumbers에 설정하여 페이지네이션 UI를 업데이트한다. 이 과정에서 paginate 함수는 새로운 페이지의 콘텐츠를 로드하기 위해 사용된다. (paginate함수는 기존 블로그 참조)

이전 및 다음 페이지로의 네비게이션 개선:

'<' 와 '>' 링크를 통해 사용자가 이전 페이지나 다음 페이지로 손쉽게 이동할 수 있으며, 첫 페이지나 마지막 페이지에서는 해당 링크가 비활성화된다.

<Link
  href="#"
  onClick={() => handleClick(currentPage - 1)}
  className={`${styles.pageLink} ${
    currentPage === 1 ? styles.disabled : null
  }`}
>
  {"<"}
</Link>

// ...

<Link
  href="#"
  onClick={() => handleClick(currentPage - 1)}
  className={`${styles.pageLink} ${
    currentPage === maxPageNum ? styles.disabled : null
  }`}
>
  {">"}
</Link>

코드 설명
Link의 onClick에 handleClick 함수가 연결되어, 페이지를 이동시킬 수 있게 한다. className prop은 동적으로 결정되며, 현재 페이지가 첫 페이지인 경우에, 첫번째 Link는 비활성화 되며, 현재 페이지가 마지막 페이지인 경우에는 두번째 Link가 비활성화 된다.

첫 페이지와 마지막 페이지로의 바로 가기:

'<<' 와 '>>' 버튼을 통해 사용자가 첫 페이지나 마지막 페이지로 즉시 이동할 수 있게 함으로써 사용자 경험을 향상시켰다. 마찬가지로 첫 페이지나 마지막 페이지에서는 해당 링크가 비활성화된다.

const goToFirstPage = () => {
  setCurrentPage(1);
  paginate(1);
};

const goToLastPage = () => {
  setCurrentPage(maxPageNum);
  paginate(maxPageNum);
};

// ...

<Link
  href="#"
  onClick={() => goToFirstPage()}
  className={`${styles.pageLink} ${
    currentPage === 1 ? styles.disabled : null
  }`}
>
  {"<<"}
</Link>

// ...

<Link
  href="#"
  onClick={() => goToLastPage ()}
  className={`${styles.pageLink} ${
    currentPage === maxPageNum ? styles.disabled : null
  }`}
>
  {">>"}
</Link>

코드 설명
이 함수들은 사용자가 첫 페이지나 마지막 페이지로 즉시 이동할 수 있게 한다. '<<'와 '>>' Link의 onClick에 이 함수들이 연결되어 있어, 사용자가 쉽게 페이지의 시작 또는 끝으로 이동할 수 있다. <>와 마찬가지로 className prop은 동적으로 결정된다.

결론

이전 페이지네이션 구현은 기본적인 요구사항을 충족시켰지만, 몇 가지 핵심 기능에서 제한점을 가지고 있었다. 사용자가 페이지네이션 내에서 더 빠르고 직관적으로 이동할 수 있도록 하는 기능이 필요했다. 이러한 여러가지 아쉬운 점들을 느껴 코드를 개선했다.

profile
프론트엔드 개발

0개의 댓글