客户端数据
本页内容

客户端数据

您可以使用 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