Пагинация в 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>
Вот и все. Вот что должно получиться.
Исходный код проекта доступен по ссылке.





Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: