用戶端資料
本頁面

用戶端資料

您可以使用 clientLoaderclientAction 函數,直接在瀏覽器中提取和變更資料。

這些函數是在使用SPA 模式時,處理資料的主要機制。本指南示範在伺服器端渲染 (SSR) 中利用用戶端資料的常見用例。

略過伺服器跳轉

當將 React Router 與後端服務前端 (BFF) 架構一起使用時,您可能希望繞過 React Router 伺服器,並直接與您的後端 API 通訊。此方法需要適當的身份驗證處理,並假設沒有 CORS 限制。以下是如何實作此操作

  1. 在文件載入時,從伺服器 loader 載入資料
  2. 在所有後續載入時,從 clientLoader 載入資料

在這種情況下,React Router 將不會在 hydration 時呼叫 clientLoader - 並且只會在後續導航時呼叫它。

export async function loader({
  request,
}: Route.LoaderArgs) {
  const data = await fetchApiFromServer({ request }); // (1)
  return data;
}

export async function clientLoader({
  request,
}: Route.ClientLoaderArgs) {
  const data = await fetchApiFromClient({ request }); // (2)
  return data;
}

全端狀態

有時您需要在渲染元件之前,結合來自伺服器和瀏覽器(例如 IndexedDB 或瀏覽器 SDK)的資料。以下是如何實作此模式

  1. 在文件載入時,從伺服器 loader 載入部分資料
  2. 匯出 HydrateFallback 元件,以便在 SSR 期間渲染,因為我們還沒有完整的資料集
  3. 設定 clientLoader.hydrate = true,這會指示 React Router 在初始文件 hydration 時呼叫 clientLoader
  4. clientLoader 中,將伺服器資料與用戶端資料結合
export async function loader({
  request,
}: Route.LoaderArgs) {
  const partialData = await getPartialDataFromDb({
    request,
  }); // (1)
  return partialData;
}

export async function clientLoader({
  request,
  serverLoader,
}: Route.ClientLoaderArgs) {
  const [serverData, clientData] = await Promise.all([
    serverLoader(),
    getClientData(request),
  ]);
  return {
    ...serverData, // (4)
    ...clientData, // (4)
  };
}
clientLoader.hydrate = true as const; // (3)

export function HydrateFallback() {
  return <p>Skeleton rendered during SSR</p>; // (2)
}

export default function Component({
  // This will always be the combined set of server + client data
  loaderData,
}: Route.ComponentProps) {
  return <>...</>;
}

選擇伺服器或用戶端資料載入

您可以在應用程式中混合資料載入策略,為每個路由選擇僅伺服器或僅用戶端的資料載入。以下是如何實作這兩種方法

  1. 當您想要使用伺服器資料時,匯出 loader
  2. 當您想要使用用戶端資料時,匯出 clientLoaderHydrateFallback

僅依賴伺服器 loader 的路由看起來像這樣

export async function loader({
  request,
}: Route.LoaderArgs) {
  const data = await getServerData(request);
  return data;
}

export default function Component({
  loaderData, // (1) - server data
}: Route.ComponentProps) {
  return <>...</>;
}

僅依賴用戶端 loader 的路由看起來像這樣。

export async function clientLoader({
  request,
}: Route.ClientLoaderArgs) {
  const clientData = await getClientData(request);
  return clientData;
}
// Note: you do not have to set this explicitly - it is implied if there is no `loader`
clientLoader.hydrate = true;

// (2)
export function HydrateFallback() {
  return <p>Skeleton rendered during SSR</p>;
}

export default function Component({
  loaderData, // (2) - client data
}: Route.ComponentProps) {
  return <>...</>;
}

用戶端快取

您可以實作用戶端快取(使用記憶體、localStorage 等)來最佳化伺服器請求。以下是一個示範快取管理的模式

  1. 在文件載入時,從伺服器 loader 載入資料
  2. 設定 clientLoader.hydrate = true 以準備快取
  3. 透過 clientLoader 從快取載入後續導航
  4. 在您的 clientAction 中使快取失效

請注意,由於我們沒有匯出 HydrateFallback 元件,我們將 SSR 路由元件,然後在 hydration 時執行 clientLoader,因此重要的是,您的 loaderclientLoader 在初始載入時傳回相同的資料,以避免 hydration 錯誤。

export async function loader({
  request,
}: Route.LoaderArgs) {
  const data = await getDataFromDb({ request }); // (1)
  return data;
}

export async function action({
  request,
}: Route.ActionArgs) {
  await saveDataToDb({ request });
  return { ok: true };
}

let isInitialRequest = true;

export async function clientLoader({
  request,
  serverLoader,
}: Route.ClientLoaderArgs) {
  const cacheKey = generateKey(request);

  if (isInitialRequest) {
    isInitialRequest = false;
    const serverData = await serverLoader();
    cache.set(cacheKey, serverData); // (2)
    return serverData;
  }

  const cachedData = await cache.get(cacheKey);
  if (cachedData) {
    return cachedData; // (3)
  }

  const serverData = await serverLoader();
  cache.set(cacheKey, serverData);
  return serverData;
}
clientLoader.hydrate = true; // (2)

export async function clientAction({
  request,
  serverAction,
}: Route.ClientActionArgs) {
  const cacheKey = generateKey(request);
  cache.delete(cacheKey); // (4)
  const serverData = await serverAction();
  return serverData;
}
文件和範例 CC 4.0