Spec-Zone.ru › Next.js

Шаблоны и лучшие практики

Существует несколько рекомендуемых шаблонов и лучших практик для извлечения данных в React и Next.js. На этой странице будут рассмотрены некоторые из самых распространённых шаблонов и способы их использования.

Получение данных на сервере

В случае возможности рекомендуется извлекать данные на сервере с помощью Server Components. Это позволяет:

  • Получить прямой доступ к ресурсам данных бэкенда (например, к базам данных).
  • Обеспечить безопасность приложения, предотвращая раскрытие конфиденциальной информации, такой как токены доступа и API-ключи, клиенту.
  • Получить данные и отобразить их в той же среде. Это сокращает как обмен данными между клиентом и сервером, так и работу в основной потоке на клиенте.
  • Выполнять несколько запросов на получение данных с одним обращением к серверу вместо нескольких отдельных запросов на клиенте.
  • Сократить «каскады» клиент-сервер запросов.
  • В зависимости от региона, получение данных может также происходить ближе к источнику данных, что уменьшает задержку и улучшает производительность.

Затем вы можете изменить или обновить данные с помощью Server Actions.

Получение данных там, где это необходимо

Если вам нужно использовать одни и те же данные (например, текущего пользователя) в нескольких компонентах в дереве, вам не нужно извлекать данные глобально или передавать свойства между компонентами. Вместо этого вы можете использовать кэширование или React кэширование в компоненте, которому нужны данные, не беспокоясь о последствиях для производительности при выполнении нескольких запросов на одни и те же данные.

Это возможно, потому что запросы автоматически запоминаются в кэше. Подробнее о запоминании запросов в кэше.

Важно знать: Это также относится к макетам, так как передача данных между родительским макетом и его дочерними компонентами невозможна.

Потоковая передача

Потоковая передача и Suspense — это функции React, которые позволяют вам поэтапно отображать и постепенно передавать рендерные блоки пользовательского интерфейса клиенту.

С помощью Server Components и вложенных макетов вы можете мгновенно отображать части страницы, которые не требуют данных, и показывать состояние ожидания для частей страницы, которые загружают данные. Это означает, что пользователю не нужно ждать полной загрузки всей страницы, прежде чем он сможет начать взаимодействовать с ней.

Server Rendering with StreamingServer Rendering with Streaming

Чтобы узнать больше о потоковой передаче и Suspense, ознакомьтесь со страницами Отображение состояния ожидания и Потоковая передача и Suspense.

Параллельное и последовательное получение данных

При получении данных внутри компонентов React вам необходимо знать два шаблона: параллельный и последовательный.

Sequential and Parallel Data FetchingSequential and Parallel Data Fetching
  • При последовательном получении данных запросы в маршруте зависят друг от друга и, следовательно, создают каскады. В некоторых случаях вам может потребоваться этот шаблон, поскольку один запрос зависит от результата другого, или вы хотите, чтобы было выполнено условие перед следующим запросом для экономии ресурсов. Однако такое поведение также может быть непреднамеренным и привести к более длительным временам загрузки.
  • При параллельном получении данных запросы в маршруте инициируются сразу и загружают данные одновременно. Это уменьшает «каскады» клиент-сервер и общее время загрузки данных.

Последовательное получение данных

Если у вас есть вложенные компоненты, и каждый компонент получает свои данные, то получение данных будет происходить последовательно, если эти запросы на получение данных разные (это не относится к запросам на получение одних и тех же данных, так как они автоматически запоминаются в кэше).

Например, компонент Playlists начнет извлекать данные только после того, как компонент Artist завершит получение данных, потому что Playlists зависит от свойства

// ...
 
async function Playlists({ artistID }: { artistID: string }) {
  // Wait for the playlists
  const playlists = await getArtistPlaylists(artistID)
 
  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}
 
export default async function Page({
  params: { username },
}: {
  params: { username: string }
}) {
  // Wait for the artist
  const artist = await getArtist(username)
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}
:

// ...
 
async function Playlists({ artistID }: { artistID: string }) {
  // Wait for the playlists
  const playlists = await getArtistPlaylists(artistID)
 
  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}
 
export default async function Page({
  params: { username },
}: {
  params: { username: string }
}) {
  // Wait for the artist
  const artist = await getArtist(username)
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}

В таких случаях вы можете использовать loading.js (для сегментов маршрута) или React <Suspense> (для вложенных компонентов), чтобы показать мгновенное состояние ожидания, пока React загружает результат.

Это предотвратит блокировку всего маршрута из-за получения данных, и пользователь сможет взаимодействовать с частями страницы, которые не заблокированы.

Блокирующие запросы на получение данных:

Альтернативный подход для предотвращения каскадов — извлекать данные глобально, в корне вашего приложения, но это заблокирует отображение всех сегментов маршрута ниже него, пока данные не загрузятся. Это можно описать как получение данных «все или ничего». Либо у вас есть все данные для вашей страницы или приложения, либо их нет.

Любые запросы на получение данных с await заблокируют отображение и получение данных для всего дерева ниже него, если они не обернуты в <Suspense> границу или не используется loading.js. Другой альтернативой является использование параллельного получения данных или шаблона предварительной загрузки.

Параллельное получение данных

Чтобы получить данные параллельно, вы можете сразу инициировать запросы, определив их вне компонентов, использующих эти данные, а затем вызывая их внутри компонента. Это экономит время, так как оба запроса инициируются параллельно, однако пользователь не увидит результат отображения, пока обе обещания не будут выполнены.

В примере ниже функции getArtist и getArtistAlbums определены вне компонента Page, а затем вызываются внутри компонента, и мы ждем выполнения обеих обещаний:

import Albums from './albums'
 
async function getArtist(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}
 
async function getArtistAlbums(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}
 
export default async function Page({
  params: { username },
}: {
  params: { username: string }
}) {
  // Initiate both requests in parallel
  const artistData = getArtist(username)
  const albumsData = getArtistAlbums(username)
 
  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData])
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}

Для улучшения пользовательского опыта вы можете добавить границу Suspense, чтобы разделить работу по отображению и показать часть результата как можно быстрее.

Предварительная загрузка данных

Другой способ предотвратить каскады — использовать шаблон предварительной загрузки. Вы можете дополнительно создать функцию preload для дальнейшей оптимизации параллельного получения данных. С этим подходом вам не нужно передавать обещания как свойства. Функция preload также может иметь любое имя, поскольку это шаблон, а не API.

import { getItem } from '@/utils/get-item'
 
export const preload = (id: string) => {
  // void evaluates the given expression and returns undefined
  // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
  void getItem(id)
}
export default async function Item({ id }: { id: string }) {
  const result = await getItem(id)
  // ...
}
import Item, { preload, checkIsAvailable } from '@/components/Item'
 
export default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  // starting loading item data
  preload(id)
  // perform another asynchronous task
  const isAvailable = await checkIsAvailable()
 
  return isAvailable ? <Item id={id} /> : null
}

Использование React cache, server-only и шаблона предварительной загрузки

Вы можете объединить функцию cache, шаблон preload, и пакет server-only для создания утилиты получения данных, которую можно использовать во всем приложении.

import { cache } from 'react'
import 'server-only'
 
export const preload = (id: string) => {
  void getItem(id)
}
 
export const getItem = cache(async (id: string) => {
  // ...
})

С этим подходом вы можете сразу получить данные, кешировать ответы и гарантировать, что это получение данных происходит только на сервере.

Экспорты utils/get-item могут использоваться макетами, страницами или другими компонентами, чтобы предоставить им контроль над временем получения данных элемента.

Важно знать:

  • Рекомендуется использовать server-only пакет, чтобы убедиться, что функции получения данных на сервере никогда не используются на клиенте.

Предотвращение раскрытия конфиденциальных данных клиенту

Рекомендуется использовать API-интерфейсы подмены React, taintObjectReference и taintUniqueValue, чтобы предотвратить передачу целых экземпляров объектов или конфиденциальных значений клиенту.

Чтобы включить подмену в вашем приложении, установите параметр конфигурации Next.js experimental.taint в experimental_taintObjectReference:

module.exports = {
  experimental: {
    taint: true,
  },
}

Затем передайте объект или значение, которое вы хотите заменить, в функции experimental_taintObjectReference или experimental_taintUniqueValue:

import { queryDataFromDB } from './api'
import {
  experimental_taintObjectReference,
  experimental_taintUniqueValue,
} from 'react'
 
export async function getUserData() {
  const data = await queryDataFromDB()
  experimental_taintObjectReference(
    'Do not pass the whole user object to the client',
    data
  )
  experimental_taintUniqueValue(
    "Do not pass the user's address to the client",
    data,
    data.address
  )
  return data
}
import { getUserData } from './data'
 
export async function Page() {
  const userData = getUserData()
  return (
    <ClientComponent
      user={userData} // this will cause an error because of taintObjectReference
      address={userData.address} // this will cause an error because of taintUniqueValue
    />
  )
}

Узнайте больше о безопасности и Server Actions.

© 2024 Vercel, Inc.
Licensed under the MIT License.
https://nextjs.org/docs/app/building-your-application/data-fetching/patterns

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API