資料獲取模式

在 React 和 Next.js 中有一些推薦的資料獲取模式和最佳實踐。本頁將介紹一些最常見的模式及其使用方法。

在伺服器端獲取資料

我們建議盡可能在伺服器端獲取資料,這樣可以:

  • 直接存取後端資料資源(例如資料庫)
  • 防止敏感資訊(如存取令牌和 API 金鑰)暴露給客戶端,從而提高應用程式安全性
  • 在同一個環境中獲取資料並渲染,減少客戶端與伺服器之間的來回通訊,同時也減少客戶端主執行緒的工作負載
  • 透過單次往返執行多次資料獲取,而不是在客戶端發送多個獨立請求
  • 減少客戶端與伺服器之間的瀑布式請求
  • 根據所在區域,資料獲取可以更靠近資料來源,從而減少延遲並提高效能

您可以使用伺服器元件、路由處理器伺服器動作在伺服器端獲取資料。

在需要的地方獲取資料

如果需要在元件樹中的多個元件中使用相同的資料(例如當前使用者),您不必全域獲取資料,也不需要在元件之間傳遞 props。相反,您可以在需要資料的元件中使用 fetch 或 React cache,而無需擔心多次請求相同資料對效能的影響。

這是因為 fetch 請求會自動被記憶化。了解更多關於請求記憶化的資訊。

小提示:這也適用於佈局,因為無法在父佈局與其子元件之間傳遞資料。

串流處理

串流處理和 Suspense 是 React 的功能,允許您逐步渲染並增量串流 UI 單元到客戶端。

透過伺服器元件和巢狀佈局,您可以立即渲染不需要特定資料的頁面部分,並為正在獲取資料的頁面部分顯示載入狀態。這意味著使用者無需等待整個頁面載入完畢即可開始與其互動。

Server Rendering with Streaming

要了解更多關於串流處理和 Suspense 的資訊,請參閱載入 UI串流處理與 Suspense 頁面。

平行與順序資料獲取

在 React 元件中獲取資料時,需要注意兩種資料獲取模式:平行與順序。

Sequential and Parallel Data Fetching
  • 順序資料獲取:路由中的請求相互依賴,因此會產生瀑布效應。有時您可能需要這種模式,因為一個請求依賴於另一個請求的結果,或者您希望在滿足條件後再進行下一個請求以節省資源。然而,這種行為也可能是無意的,導致載入時間變長。
  • 平行資料獲取:路由中的請求會立即啟動並同時載入資料。這減少了客戶端與伺服器之間的瀑布效應以及載入資料的總時間。

順序資料獲取

如果有巢狀元件,且每個元件都獲取自己的資料,那麼如果這些資料請求不同(這不適用於請求相同資料的情況,因為它們會自動記憶化),資料獲取將按順序進行。

例如,Playlists 元件只有在 Artist 元件完成資料獲取後才會開始獲取資料,因為 Playlists 依賴於 artistID prop:

// ...

async function Playlists({ artistID }: { artistID: string }) {
  // 等待播放清單
  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 }
}) {
  // 等待藝術家資料
  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。另一種選擇是使用平行資料獲取預載入模式

平行資料獲取

要平行獲取資料,可以在使用資料的元件外部定義請求,然後在元件內部呼叫它們。這樣可以透過平行啟動兩個請求來節省時間,但使用者在兩個 promise 都解析完成之前不會看到渲染結果。

在下面的範例中,getArtistgetArtistAlbums 函數在 Page 元件外部定義,然後在元件內部呼叫,我們等待兩個 promise 解析:

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 解析
  const [artist, albums] = await Promise.all([artistData, albumsData])

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}

為了提升使用者體驗,您可以添加 Suspense 邊界來分割渲染工作,並盡快顯示部分結果。

預載入資料

防止瀑布效應的另一種方法是使用預載入模式。您可以選擇性地建立一個 preload 函數來進一步最佳化平行資料獲取。透過這種方法,您無需將 promise 作為 props 傳遞。preload 函數也可以使用任何名稱,因為它是一種模式,而不是 API。

import { getItem } from '@/utils/get-item'

export const preload = (id: string) => {
  // void 會執行給定的表達式並返回 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)
  // ...
}

使用 React cacheserver-only 和預載入模式

您可以結合 cache 函數、預載入模式和 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 套件來確保伺服器端資料獲取函數永遠不會在客戶端使用。

On this page