Разбиение на страницы — это обычная функция веб-приложений, которая позволяет пользователям перемещаться по большому набору данных, разделяя его на более мелкие и более управляемые фрагменты. В Next.js 13 нумерацию страниц можно реализовать с помощью комбинации рендеринга на стороне сервера и логики на стороне клиента. Чтобы упростить процесс, мы будем использовать Jotai для управления состоянием различных компонентов и страниц Next.js, чтобы сохранять прогресс разбиения на страницы пользователя.
Примечание: поскольку Next.js 13 сейчас находится в стадии бета-тестирования, описанный здесь код может не работать в будущем.
Настройка проекта и получение наших данных
Для начала вам нужно установить Next.js 13 и настроить новый проект.
Убедитесь, что вы используете экспериментальный appDir, установив для него значение true, если вы читаете это, пока Next.js 13 все еще находится в стадии бета-тестирования. Я могу обновить это в будущем, если в этом больше нет необходимости.
Также нам нужно установить Jotai с npm. Просто запустите npm i jotai
в корневой папке вашего проекта Next.js, и все готово.
Когда у вас есть базовое приложение Next.js с запущенным и работающим Jotai, мы можем начать с получения наших сообщений в блогах с помощью fetch()
. В Next.js 13 это новый упрощенный способ получения данных в вашем приложении. В зависимости от того, как структурированы данные, которые вы извлекаете, вам может потребоваться немного проб и ошибок, хотя я приложил все усилия, чтобы сделать это руководство универсальным.
// app/page.jsx /* here we fetch our blog posts and pass them as props to the BlogPostList Component */ export async function getPosts() { const res = await fetch('https://api.example.com/...'); return res.json(); } /* note that we use async/await here, which was not possible in previous versions of next.js */ export default async function Home() { const posts = await getPosts(); return ( <div></div> ) }
getServerSideProps, getStaticProps, getInitialProp больше не поддерживаются в новом каталоге приложений Next.js 13, поэтому мы используем fetch()
Как только мы получим наши сообщения от нашей конечной точки на нашей странице, как в приведенном ниже коде, нам нужно создать новый функциональный компонент и импортировать его в наш page.jsx. Я назову его BlogPostList.jsx и передам сообщения в качестве реквизита сообщений.
// app/page.jsx import BlogPostList from '../components/BlogPostList' /* here we fetch our blog posts and pass them as props to the BlogPostList Component */ export async function getPosts() { const res = await fetch('https://api.example.com/...'); return res.json(); } /* note that we use async/await here, which was not possible in previous versions of next.js */ export default async function Home() { const posts = await getPosts(); return ( <div> <BlogPostList posts={posts}/> </div> ) }
Если вы получили свои данные из используемого вами API и передали их компоненту BlogPostList.jsx
, вы можете деструктурировать свойства в объявлении функции и зарегистрировать их, чтобы увидеть, все ли работает.
Теперь у вас должен быть доступ к вашему списку сообщений в компоненте BlogPostList.jsx.
Имейте в виду, что ваши console.logs будут зарегистрированы на сервере, когда вы захотите записать их из page.jsx, поэтому вы не увидите их в своем браузере. Если вы не используете клиентский компонент, вам следует проверить терминал, в котором вы запустили сервер разработки, чтобы убедиться, что ваш журнал содержит нужные вам данные.
Реализация методов slice() и map() и настройка каталогов маршрутов
Обратите внимание, что наш BlogPostList.jsx содержит строку «использовать клиент» в верхней части кода. Это позволяет нам использовать стандартные хуки реагирования, такие как, например, useState() и обработчики событий, такие как onClick в нашем JSX, поскольку они будут работать только на клиенте. Если бы мы попытались использовать эти методы на серверном компоненте Next.js, они бы не сработали.
// components/BlogPostList.jsx 'use client' import React from 'react' import Link from 'next/link' export default function BlogPostList({ posts }) { return ( <> <div> {/* we will specify the values of the slice params in the next steps */} {posts.slice(currentSliceStart, currentSliceEnd).map((posts) => ( <div key={posts.id}> <Link href={`/blog/${posts.id}`}> <h1>{posts.postTitle}</h1> <p>{posts.previewText}</p> </Link> </div> ))} </div> {/* button loads two more posts on load posts and disappears if no more posts can be loaded */} {currentSliceEnd < posts.items.length && <button onClick={nextPage}>Load more posts</button>} </> ) }
Чтобы позже разбить наши сообщения в блоге на страницы, мы используем метод slice() для массива сообщений. Мы передадим методу две переменные с состоянием: currentSliceStart и currentSliceEnd. Они будут указывать, какой диапазон сообщений мы хотим отображать в нашем компоненте. Прикрепленный метод map() будет затем отображать только указанные сообщения, которые находятся в пределах диапазона среза страницы. Используя ссылку Next.js, мы можем динамически создавать ссылки на различные посты-подстраницы. Обязательно импортируйте ссылку в верхней части компонента из next/link.
Чтобы динамические маршруты работали, нам нужно создать два новых каталога внутри компонента приложения. Каталог блога и каталог [post]. Обратите внимание, как каталог [post] заключен в фигурные скобки. Это означает, что это динамический маршрут. Для каждого идентификатора, отображаемого в нашем BlogPostList, будет создан новый маршрут.
Добавление состояния с помощью Jotai и предоставление кнопок страницы
Сначала нам нужно создать каталог хранения в нашем корне, в котором мы создадим файлatomic.js.
Там мы создадим наше состояние, используя так называемые атомы.
// storage/atom.js import { atom } from 'jotai'; const sliceStartAtom = atom(0) const sliceEndAtom = atom(4) const currentPageAtom = atom(1) export { sliceStartAtom, sliceEndAtom, currentPageAtom };
Вот и все. Мы можем использовать эти значения в каждом клиентском компоненте на протяжении всего проекта.
Чтобы использовать атомы, мы импортируем их в наш BlogPostList.jsx и используем пользовательский useAtom() -Hook. useAtom() работает аналогично useState(), но может получать и устанавливать значения из любого места в нашем приложении. По сути, это useState() для глобальной установки состояния.
// components/BlogPostList.jsx 'use client' import React from 'react' import Image from 'next/image' import Link from 'next/link' import { sliceStartAtom, sliceEndAtom, currentPageAtom } from '../storage/atoms' import { useAtom } from 'jotai'
Чтобы перейти к следующей странице в нашем списке блогов, мы просто добавим к currentSliceStart и currentSliceEnd, чтобы указать новый диапазон сообщений, которые должны быть отображены. Мы делаем это, вызывая функцию nextPage(), которая просто добавляет указанное целое число к текущему состоянию с помощью setter-Functions, объявленных в хуках useAtom(). Функция previousPage() работает так же, как функция nextPage(), но вычитает из текущих состояний.
// components/BlogPostList.jsx ... // using the global state from Jotai for setting our slice values const [currentSliceStart, setCurrentSliceStart] = useAtom(sliceStartAtom) const [currentSliceEnd, setCurrentSliceEnd] = useAtom(sliceEndAtom) const [currentPage, setCurrentPage] = useAtom(currentPageAtom) // the number that is added to the states specifies how many posts are displayed per page const nextPage = () => { setCurrentSliceStart(currentSliceStart + 4) setCurrentSliceEnd(currentSliceEnd + 4) setCurrentPage(currentPage + 1) } const previousPage = () => { setCurrentSliceStart(currentSliceStart - 4) setCurrentSliceEnd(currentSliceEnd - 4) setCurrentPage(currentPage - 1) } ...
Чтобы избежать добавления или вычитания слишком многого из наших атомов, мы просто отключим кнопки, которые вызывают nextPage() и previousPage() позже, если они превысят определенные значения или потенциально упадут ниже нуля. Мы хотим оставаться между нулем и длиной нашего массива сообщений.
// components/BlogPostList.jsx 'use client' import React from 'react' import Image from 'next/image' import Link from 'next/link' import { sliceStartAtom, sliceEndAtom, currentPageAtom } from '../storage/atoms' import { useAtom } from 'jotai' export default function BlogPostList({ posts }) { // using the global state from Jotai for setting our slice values const [currentSliceStart, setCurrentSliceStart] = useAtom(sliceStartAtom) const [currentSliceEnd, setCurrentSliceEnd] = useAtom(sliceEndAtom) const [currentPage, setCurrentPage] = useAtom(currentPageAtom) // the number that is added to the states specifies how many posts are displayed per page const nextPage = () => { setCurrentSliceStart(currentSliceStart + 4) setCurrentSliceEnd(currentSliceEnd + 4) setCurrentPage(currentPage + 1) } const previousPage = () => { setCurrentSliceStart(currentSliceStart - 4) setCurrentSliceEnd(currentSliceEnd - 4) setCurrentPage(currentPage - 1) } return ( <> <div> {posts.slice(currentSliceStart, currentSliceEnd).map((posts) => ( <div key={posts.id}> <Link href={`/blog/${posts.id}`}> <h1>{posts.postTitle}</h1> <p>{posts.previewText}</p> </Link> </div> ))} </div> {currentSliceStart >= 4 && <button onClick={previousPage}>previous</button>} {currentSliceEnd < posts.items.length && <button onClick={nextPage}>next</button>} </> ) }
Вот и все. Теперь вы должны иметь возможность перемещаться по своим сообщениям, нажимая кнопки «предыдущий» и «следующий», если ваш массив сообщений содержит более 4 элементов. Если вы хотите, чтобы шаги были меньше или больше, вы можете просто настроить числа в функциях nextPage и previousPage и настроить условия перед кнопками в jsx.
Если вы используете только currentSliceEnd и добавляете к нему при жестком кодировании начала среза в 0, вы также можете постоянно загружать больше сообщений по мере добавления в состояние currentSliceEnd вместо использования разбивки на страницы. Если вы добавите в setCurrentSliceEnd реализацию функции прокрутки, которая срабатывает, если пользователь находится ближе к концу страницы, это также может быть встроено в своего рода функциональность бесконечной прокрутки.
Если вам нужна помощь или есть рекомендации по улучшению кода, оставьте комментарий или напишите мне.
Я также могу предоставить доступ к github-репозиторию, если это необходимо, который содержит весь код и полностью построенный шаблон блога, который использует Contentful в качестве CMS.