Разбиение на страницы — это обычная функция веб-приложений, которая позволяет пользователям перемещаться по большому набору данных, разделяя его на более мелкие и более управляемые фрагменты. В 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.

Блог работает, посетите здесь