[Next 13] Data Fetching - Data Fetching Patterns

Jeongho·2023년 9월 6일
0
  • React 및 Next.js에서 데이터를 가져오기 위한 몇 가지 권장 패턴과 모범 사례가 있습니다. 이 페이지에서는 가장 일반적인 패턴 중 일부와 사용 방법을 살펴보겠습니다.

서버에서 데이터 가져오기

  • 가능하다면 서버에서 데이터를 가져오는 것이 좋습니다. 이를 통해 다음을 수행할 수 있습니다.
    • 백엔드 데이터 리소스(예: 데이터베이스)에 직접 액세스할 수 있습니다.
    • 액세스 토큰, API 키 등 민감한 정보가 클라이언트에 노출되는 것을 방지하여 애플리케이션을 더욱 안전하게 유지할 수 있습니다.
    • 동일한 환경에서 데이터를 가져오고 렌더링합니다. 이렇게 하면 클라이언트와 서버 간의 앞뒤 통신은 물론 클라이언트의 기본 스레드 작업도 줄어듭니다.
    • 클라이언트에서 여러 개별 요청을 수행하는 대신 단일 왕복으로 여러 데이터 가져오기를 수행합니다.
    • client - server waterfalls가 줄어듭니다.
    • 지역에 따라 데이터 가져오기가 데이터 소스에 더 가까운 곳에서 발생하여 대기 시간이 줄어들고 성능이 향상될 수도 있습니다.
  • 서버 컴포넌트, Route Handler 및 서버 작업을 사용하여 서버에서 데이터를 가져올 수 있습니다.

필요한 곳에서 데이터 가져오기

  • 트리의 여러 컴포넌트에서 동일한 데이터(예: 현재 사용자)를 사용해야 하는 경우 전역적으로 데이터를 가져오거나 컴포넌트 간에 props을 전달할 필요가 없습니다.
  • 대신 동일한 데이터에 대해 여러 번 요청하는 경우 성능에 미치는 영향을 걱정하지 않고 데이터가 필요한 컴포넌트에서 fetch 또는 React 캐시를 사용할 수 있습니다.
  • 이는 fetch 요청이 자동으로 메모되기 때문에 가능합니다. 메모이제이션 요청에 대해 자세히 알아보기
  • 알아두면 좋은 점
    • 상위 레이아웃과 하위 레이아웃 간에 데이터를 전달할 수 없기 때문에 이는 레이아웃에도 적용됩니다.

Streaming (스트리밍)

  • 스트리밍 및 Suspense는 클라이언트에 UI의 렌더링된 단위를 점진적으로 렌더링하고 증분적으로 스트리밍할 수 있게 해주는 React 기능입니다.
  • 서버 컴포넌트 및 중첩 레이아웃을 사용하면 특별히 데이터가 필요하지 않은 페이지 부분을 즉시 렌더링하고 데이터를 가져오는 페이지 부분에 대한 로드 상태를 표시할 수 있습니다. 즉, 사용자는 상호 작용을 시작하기 전에 전체 페이지가 로드될 때까지 기다릴 필요가 없습니다.

병렬 및 순차 데이터 가져오기

  • React 컴포넌트 내에서 데이터를 가져올 때 병렬 및 순차라는 두 가지 데이터 가져오기 패턴을 알아야 합니다.
  • 순차적 데이터 fetch를 사용하면 경로의 요청이 서로 종속되므로 폭포가 생성됩니다. 한 fetch가 다른 fetch의 결과에 따라 달라지기 때문에 이 패턴을 원하거나 리소스를 절약하기 위해 다음 fetch 전에 조건이 충족되기를 원하는 경우가 있을 수 있습니다. 그러나 이 동작은 의도하지 않은 것일 수도 있으며 로딩 시간이 길어질 수도 있습니다.
  • 병렬 데이터 fetch를 사용하면 경로의 요청이 즉시 시작되고 동시에 데이터가 로드됩니다. 이렇게 하면 클라이언트-서버 폭포수와 데이터를 로드하는 데 걸리는 총 시간이 줄어듭니다.

순차적으로 데이터 가져오기

  • 중첩된 컴포넌트가 있고 각 컴포넌트가 자체 데이터를 가져오는 경우 해당 데이터 요청이 다르면 데이터 가져오기가 순차적으로 발생합니다(자동으로 메모되므로 동일한 데이터에 대한 요청에는 적용되지 않습니다).
  • 예를 들어, Playlists 컴포넌트는 Artist 컴포넌트가 데이터 가져오기를 완료한 후에만 데이터 가져오기를 시작합니다. 왜냐하면 재생 목록은 ArtistID props에 의존하기 때문입니다.
// app/artist/page.tsx
// ...
 
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가 결과를 스트리밍하는 동안 즉각적인 로딩 상태를 표시할 수 있습니다.
  • 이렇게 하면 데이터 가져오기로 인해 전체 경로가 차단되는 것을 방지할 수 있으며 사용자는 차단되지 않은 페이지 부분과 상호 작용할 수 있습니다.
  • 데이터 요청 차단
    • waterfalls를 방지하는 또 다른 접근 방식은 애플리케이션의 루트에서 전역적으로 데이터를 가져오는 것입니다. 하지만 이렇게 하면 데이터 로드가 완료될 때까지 그 아래의 모든 경로 세그먼트에 대한 렌더링이 차단됩니다. 이는 "전부 아니면 전무" 데이터 가져오기로 설명할 수 있습니다. 페이지나 애플리케이션에 대한 전체 데이터가 있거나 전혀 없습니다.
    • Wait가 있는 가져오기 요청은 <Suspense> 경계에 래핑되거나 loading.js가 사용되지 않는 한 그 아래의 전체 트리에 대한 렌더링 및 데이터 가져오기를 차단합니다. 또 다른 대안은 병렬 데이터 가져오기 또는 사전 로드 패턴을 사용하는 것입니다.

병력으로 데이터 가져오기

  • 데이터를 병렬로 가져오려면 데이터를 사용하는 컴포넌트 외부에서 요청을 정의한 다음 컴포넌트 내부에서 호출하여 요청을 적극적으로 시작할 수 있습니다.
  • 이렇게 하면 두 요청을 병렬로 시작하여 시간이 절약되지만 두 Promise가 모두 해결될 때까지 사용자는 렌더링된 결과를 볼 수 없습니다.
  • 아래 예에서 getArtist 및 getArtistAlbums 함수는 페이지 컴포넌트 외부에서 정의된 다음 컴포넌트 내부에서 호출되며 두 Promise가 모두 해결될 때까지 기다립니다.
// app/artist/[username]/page.tsx
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 }
}) {
  // 두 요청을 동시에 시작
  const artistData = getArtist(username)
  const albumsData = getArtistAlbums(username)
 
  // Promise가 resolve를 반환할 때까지 대기
  const [artist, albums] = await Promise.all([artistData, albumsData])
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}
  • 사용자 경험을 향상시키기 위해 Suspense Boundary를 추가하여 렌더링 작업을 분할하고 결과의 일부를 최대한 빨리 표시할 수 있습니다.

Preloading Data

  • waterfalls를 방지하는 또 다른 방법은 preload 패턴을 사용하는 것입니다. 선택적으로 preload 기능을 생성하여 병렬 데이터 가져오기를 더욱 최적화할 수 있습니다.
  • 이 접근 방식을 사용하면 Promise를 props로 전달할 필요가 없습니다. preload 함수는 API가 아닌 패턴이므로 어떤 이름이라도 가질 수 있습니다.
// components/item.tsx
import { getItem } from '@/utils/get-item'
 
export const preload = (id: string) => {
  // void evaluates the given expression and returns undefined
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
  void getItem(id)
}
export default async function Item({ id }: { id: string }) {
  const result = await getItem(id)
  // ...
}
// app/item/[id]/page.tsx
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 캐시, 서버 전용 및 Preload 패턴 사용

  • 캐시 기능, preload 패턴 및 서버 전용 패키지를 결합하여 앱 전체에서 사용할 수 있는 데이터 가져오기 유틸리티를 만들 수 있습니다. (유용하네요)
// utils/get-item.ts
import { cache } from 'react'
import 'server-only'
 
export const preload = (id: string) => {
  void getItem(id)
}
 
export const getItem = cache(async (id: string) => {
  // ...
})
  • 이 접근 방식을 사용하면 데이터를 적극적으로 가져오고 응답을 캐시하며 이 데이터 fetch가 서버에서만 발생하도록 보장할 수 있습니다.
  • utils/get-item export는 레이아웃, 페이지 또는 기타 컴포넌트에서 항목의 데이터를 가져오는 시기를 제어하는 데 사용할 수 있습니다.
  • 알아두면 좋은 점
    • 서버 데이터 fetch 기능이 클라이언트에서 사용되지 않도록 하려면 서버 전용 패키지를 사용하는 것이 좋습니다.

Reference

profile
주도적으로 문제를 정의하고 코드를 통해 해결합니다.

0개의 댓글