Пагинация в React: полезный кастомный хук
Фулстек-разработчик Дамилола Йоруба опубликовал в блоге туториал по разбивке страниц с помощью React-хука. Он призван помочь разобраться в концепции пагинации и в том, как ее реализовать на React. Изученный метод может быть применен к любому проекту на JavaScript.
В чем суть
Пагинация — это процесс разделения печатного или цифрового контента на отдельные страницы. Для печатных документов и онлайн-контента пагинация также относится к автоматизированному процессу добавления последовательных номеров для определения последовательного порядка страниц.
Например, на странице шесть элементов, а нужно отобразить только три. Это значит, что будет две страницы, а если нужно отображать по два элемента на странице, то всего будет три страницы.
Формула проста:
totalPages = totalContent / contentPerPage
Метод .slice()
в JavaScript
После вычисления количества элементов нужно определить, какой контент отображать на конкретной странице. Для этого необходимо понять, как взаимодействуют страницы и индекс содержимого, поэтому сначала нужно разобраться с методом .slice()
.
Метод slice()
копирует заданную часть массива и возвращает эту скопированную часть в виде нового массива. Исходный массив при этом не изменяется.
Например, есть массив под названием scouts
, и нужно выбрать только часть этого массива, опираясь на индекс массива.
const scouts = ["levi", "hange", "erwin", "petra", "oruo", "miche"] scouts.slice(2, 5) // output: [ 'erwin', 'petra', 'oruo' ] scouts.slice(1, 3) // output: [ 'hange', 'erwin' ]
Массивы в JavaScript индексируются с нуля, поэтому первый параметр — это индекс, с которого нужно начать срез, а второй параметр — это индекс сразу после того места, где нужно, чтобы срез закончился. Например, если нужен срез от 2 до 4, используется .slice(2, 5)
, как показано на примере выше.
Сопоставление номера страницы с индексом
Все, что нужно сделать, это узнать, какими должны быть startIndex
и lastIndex
на основе номера страницы.
Пример выше показывает, что lastIndex
—это просто текущая страница, умноженная на заданное содержание страницы, а startIndex
— это содержание страницы, вычтенное из lastIndex
.
// assuming we are on page one const page = 1; const contentPerPage = 3 const lastIndex = page * contentPerPage // 3 const firstIndex = lastIndex - contentPerPage // 0 scouts.slice(firstIndex, lastIndex) // scouts.slice(0, 3) => [ 'levi', 'hange', 'erwin' ] // page 2 // scouts.slice(3, 6) => [ 'petra', 'oruo', 'miche' ]
Кастомный хук usePagination
Этот хук принимает объект, который принимает свойства contentPerPage
— сколько элементов должно отображаться за один раз и count
— общее количество заданных элементов (длина массива). Он также возвращает объект со следующими свойствами:
page
— текущая страница, на которой находится пользователь;totalPages
— общее количество сгенерированных страниц;firstContentIndex
— первый индекс для метода.slice()
;lastContentIndex
— последний индекс для метода.slice()
;nextPage
— функция для перехода на одну страницу вперед;prevPage
— функция для перехода на одну страницу назад;setPage
— функция для перехода на определенную страницу.
Значения типов:
interface UsePaginationProps { contentPerPage: number, count: number, } interface UsePaginationReturn { page: number; totalPages: number; firstContentIndex: number; lastContentIndex: number; nextPage: () => void; prevPage: () => void; setPage: (page: number) => void; } type UsePagination = (UsePaginationProps) => (UsePaginationReturn);
Далее необходимо создать в проекте React папку hooks
и файл usePagination
, в котором будет находиться хук.
Напишите в нем следующее:
import { useState } from "react"; const usePagination: UsePagination = ({ contentPerPage, count }) => { const [page, setPage] = useState(1); // number of pages in total (total items / content on each page) const pageCount = Math.ceil(count / contentPerPage); // index of last item of current page const lastContentIndex = page * contentPerPage; // index of first item of current page const firstContentIndex = lastContentIndex - contentPerPage; // change page based on direction either front or back const changePage = (direction: boolean) => { setPage((state) => { // move forward if (direction) { // if page is the last page, do nothing if (state === pageCount) { return state; } return state + 1; // go back } else { // if page is the first page, do nothing if (state === 1) { return state; } return state - 1; } }); }; const setPageSAFE = (num: number) => { // if number is greater than number of pages, set to last page if (num > pageCount) { setPage(pageCount); // if number is less than 1, set page to first page } else if (num < 1) { setPage(1); } else { setPage(num); } }; return { totalPages: pageCount, nextPage: () => changePage(true), prevPage: () => changePage(false), setPage: setPageSAFE, firstContentIndex, lastContentIndex, page, }; }; export default usePagination;
Значение текущей страницы управляется с помощью useState
. Следует обратить внимание, что pageCount
также равен значению последней страницы.
Реализация хука
Для реализации хука его нужно импортировать, а затем ввести необходимые свойства.
... const { firstContentIndex, lastContentIndex, nextPage, prevPage, page, setPage, totalPages, } = usePagination({ contentPerPage: 3, count: people.length, }); ...
Затем данные просто срезаются с помощью firstContentIndex
и lastContentIndex
.
... <div className="items"> {people .slice(firstContentIndex, lastContentIndex) .map((el: any) => ( <div className="item" key={el.uid}></div> ))} </div> ...
Код ниже позволяет создать кнопки, а также добавить соответствующие дескрипторы onClick
.
<div className="pagination"> <p className="text"> {page}/{totalPages} </p> <button onClick={prevPage} className="page"> ← </button> {/* @ts-ignore */} {[...Array(totalPages).keys()].map((el) => ( <button onClick={() => setPage(el + 1)} key={el} className={`page ${page === el + 1 ? "active" : ""}`} > {el + 1} </button> ))} <button onClick={nextPage} className="page"> → </button> </div>
Вот и все. Вот что должно получиться.
Исходный код проекта доступен по ссылке.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: