客户端数据
本页内容

客户端数据



您可以使用 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 在初始文档水合过程中调用 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. 当您想使用客户端数据时,导出 clientLoader 和一个 HydrateFallback

一个仅依赖于服务器加载器的路由如下所示

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 <>...</>;
}

一个仅依赖于客户端加载器的路由如下所示。

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,然后在水合时运行 clientLoader,因此重要的是您的 loaderclientLoader 在初始加载时返回相同的数据,以避免水合错误。

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
编辑