랭킹을 나타내는 테이블에서 데이터를 한 번에 많이 불러오면 사람들이 보기도 안좋으니, 페이지네이션 기능을 추가하여, 클릭할 수 있도록 구현하였다.
export default function Ranking(context: InferGetStaticPropsType<typeof getStaticProps>) {
const [rank, setRank] = useState<RankProps[]>([])
// 한 페이지 당 20개의 순위 보여주기
const [limit, setLimit] = useState(10);
// 현재 페이지
const [page, setPage] = useState(1);
// 첫 페이지의 위치
const offset = (page - 1) * limit;
...
return (
...
<RankTable
page={page}
setPage={setPage}
data={filterArticles(searchValue)}
offset={offset}
limit={limit} />
...
)
}
limit : 한 페이지 당 보여줄 랭킹 개수,
page : 현재 페이지
offest : 페이지의 시작 위치를 말함.
page : 현재 보고 있는 페이지 번호
offset는 해당 페이지의 시작 위치
한 페이지에 10개의 데이터를 보여줄 때, 첫 번째 페이지는 offset이 0이 되고, 두 번째 페이지는 offset이 10이 되는 식이다. page와 offset을 분리하여 관리하면, 페이지 전환 시 offset을 계산하는 과정이 간편해지며, 데이터를 효율적으로 처리할 수 있다. offset을 계산하면서 발생하는 오류를 방지하기 위해 page와 offset을 분리하여 관리한다.
offset는 보통 데이터를 조회할 때 특정위치에서부터 시작하는 위치를 나타내는 값이다. 예를 들어, 한 페이지 당 10개의 데이터를 보여줄 때, 첫 번째 페이지는 0부터 9까지의 데이터를 보여주며, 두 번째 페이지는 10부터 19까지의 데이터를 보여준다. 이 때, 첫 번째 페이지의 시작 위치 0이나 두 번째 페이지의 시작 위치 10이 ‘offset’ 값이 된다. 따라서 ‘offset’ 값은 페이지를 나타내는 숫자와는 다르게, 데이터의 위치를 나타내는 숫자이다. offset을 이용하여 특정 위치에서부터 데이터를 조회할 수 있다.
offset값은 데이터의 위치를 나타내는 값이기 때문에, 배열의 인덱스와 유사한 개념이다. 따라서 ‘offset’ 값이 0이면, 배열의 첫 번째 요소에 해당하는 데이터를 조회할 수 있으며, ‘offset’ 값이 1이면 두 번째 요소에 해당하는 데이터를 조회할 수 있다. 하지만, 배열의 인덱스와는 다르게 ‘offset’값은 페이지별로 구분되는 데이터를 처리할 때 주로 사용된다.
import Pagination from "./Pagination.jsx";
import { RankProps } from "../../types/data";
type Props = {
data: RankProps[];
offset: number;
limit: number;
page: number;
setPage: React.Dispatch<React.SetStateAction<number>>;
};
const RankTable = ({ data, offset, limit, page, setPage }: Props) => {
const thead = ["순위", "이름", "학번", "학과(부)", "점수", "순위변동"]
let i = 1;
return (
<>
<div className="flex flex-col overflow-x-auto">
<div className="sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 sm:px-6 lg:px-8">
<div className="overflow-x-auto">
<table className="min-w-full text-left text-sm font-light">
<thead className="border-b bg-gray-100 font-medium ">
<tr>
{thead.map((name) => (
<th key={name}
scope="col"
className="px-6 py-4"
>
{name}
</th>
))}
</tr>
</thead>
{/* 데이터 배열 자르기 */}
<tbody>
{data.slice(offset, offset + limit).map((item) => (
<tr key={item.name}
className="border-b">
{/* 페이지 + 넘긴 값들 더하기 */}
<td className="whitespace-nowrap px-6 py-4 font-medium">
{i++ + offset}
</td>
<td className="whitespace-nowrap px-6 py-4" >{item.name}</td>
<td className="whitespace-nowrap px-6 py-4">{item.studentId}</td>
<td className="whitespace-nowrap px-6 py-4">
{item.division}
</td>
<td className="whitespace-nowrap px-6 py-4">
{item.score}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
<footer>
<Pagination
total={data.length}
limit={limit}
page={page}
setPage={setPage}
/>
</footer>
</>
);
};
export default RankTable;
data는 학생들의 정보를 담고 있는 RankProps[] 배열이며, offset는 페이지당 보여줄 학생 수를 결정하는 limit과 함께 현재 페이지의 첫 번째 학생 인덱스를 결정한다. page는 현재 보여지는 페이지를 나타내며, setPage는 페이지를 변경할 때 호출하는 함수다. data 배열을 자른 뒤 각각의 학생 데이터를 행으로 표시한다. Pagination 컴포넌트를 렌더링하여 페이지 이동을 가능하게 한다.
{i++ + offset}는 테이블의 각 항목의 순위 번호를 표시하는 데 사용한다. 코드에서 i의 초기값을 1로 설정하였다. data 배열을 반복하면서 i를 사용하여 각 항목의 순위 번호를 표시한다. 그러나 페이지당 제한된 수의 항목을 표시하기 위해 페이지 매김을 사용하고 있으므로 현재 페이지에서 첫 번째 항목의 위치를 나타내는 ‘offset’값도 고려해야한다.
예를 들어, 두 번째 페이지에 있고, 페이지 당 10개의 항목을 표시하는 경우 ‘offset’의 값은 10이다. 이 경우 두 번째 페이지의 첫 번째 항목의 순위는 11(즉, 1+10)이다. 12(2+10) 등 이다.
{i++ + offset} 표현식을 사용하여 값을 표시한 후 i를 증가시켜 다음 항목의 순위가 한 단계 높아진다. 또한 현재 페이지와 페이지당 표시되는 항목 수를 고려하기 위해 offset 값을 추가하고 있다.
function Pagination({ total, limit, page, setPage }) {
const numPages = Math.ceil(total / limit);
return (
<nav className="my-10">
<div className="inline-flex items-center -space-x-px">
<button
className="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700"
onClick={() => setPage(page - 1)}
disabled={page === 1}
>
<
</button>
{/* 현재 페이지로 이동 */}
{Array(numPages)
.fill()
.map((_, i) => (
<button
className="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700"
key={i + 1}
onClick={() => setPage(i + 1)}
aria-current={page === i + 1 ? "page" : null}
>
{i + 1}
</button>
))}
<button
className="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700"
onClick={() => setPage(page + 1)}
disabled={page === numPages}
>
>
</button>
</div>
</nav>
);
}
export default Pagination;
총 데이터 수(total), 한 페이지에 보여질 데이터 수(limit), 현재 페이지 수(page), 페이지 수를 변경할 수 있는 함수(setPage)를 인자로 받아 페이지네이션을 구성한다.
numPages 변수를 이용하여 전체 페이지 수를 계산한다. 그 다음, 화면에 보여지는 버튼들을 생성하는데, 배열의 fill() 함수와 map() 함수를 이용하여, 페이지 수(numPages)에 따라 버튼을 동적으로 생성한다.
각 페이지 버튼은 onClick 이벤트 핸들러를 통해서 이동하고, 현재 페이지와 같은지에 따라 스타일을 다르게 표시한다. 이전 페이지, 다음 페이지로 이동하는 버튼은 현재 페이지가 1이거나 마지막 페이지인 경우 비활성화되도록 구현한다.
인자로 전달된 값을 배열의 요소로 채우는 역할을 하다. 예를 들어 Array(5),fill(0)을 실행하면, [0, 0, 0, 0, 0]이라는 5개의 요소를 가진 배열을 만들 수 있다. 이 코드에서는 전체 페이지 수(numPages)만큼의 길이를 가진 배열을 생성하고, 각각의 요소는 undefined로 설정된다. 이후 map()함수를 사용하여, 배열의 요소 수만큼 버튼을 생성하는데, 버튼의 텍스트 값은 각 요소의 인덱스 값에 1을 더한 값으로 설정된다. 즉, 페이지 번호를 버튼으로 만들기 위해 fill()함수를 사용한다.