数据加载
在本页

数据加载

引言

数据通过 loaderclientLoader 提供给路由组件。

Loader 数据会自动从 loader 中序列化,并在组件中反序列化。除了字符串和数字等基本类型值之外,loader 还可以返回 Promise、Map、Set、Date 等。

loaderData 属性的类型是自动生成的。

客户端数据加载

clientLoader 用于在客户端获取数据。这对于那些你希望仅从浏览器获取数据的页面或整个项目很有用。

// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";

export async function clientLoader({
  params,
}: Route.ClientLoaderArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  const product = await res.json();
  return product;
}

// HydrateFallback is rendered while the client loader is running
export function HydrateFallback() {
  return <div>Loading...</div>;
}

export default function Product({
  loaderData,
}: Route.ComponentProps) {
  const { name, description } = loaderData;
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

服务端数据加载

在服务器渲染时,loader 用于初始页面加载和客户端导航。客户端导航会通过 React Router 从浏览器到服务器的自动 fetch 调用 loader。

// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
import { fakeDb } from "../db";

export async function loader({ params }: Route.LoaderArgs) {
  const product = await fakeDb.getProduct(params.pid);
  return product;
}

export default function Product({
  loaderData,
}: Route.ComponentProps) {
  const { name, description } = loaderData;
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

请注意,loader 函数会从客户端打包文件中移除,因此你可以放心地使用仅限服务器的 API,而无需担心它们会被包含在浏览器中。

静态数据加载

在预渲染时,loader 用于在生产构建期间获取数据。

// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";

export async function loader({ params }: Route.LoaderArgs) {
  let product = await getProductFromCSVFile(params.pid);
  return product;
}

export default function Product({
  loaderData,
}: Route.ComponentProps) {
  const { name, description } = loaderData;
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

要预渲染的 URL 在 react-router.config.ts 中指定

import type { Config } from "@react-router/dev/config";

export default {
  async prerender() {
    let products = await readProductsFromCSVFile();
    return products.map(
      (product) => `/products/${product.id}`
    );
  },
} satisfies Config;

请注意,在服务器渲染时,任何未预渲染的 URL 将照常进行服务器渲染,这允许你在单个路由预渲染部分数据,同时仍对其余数据进行服务器渲染。

同时使用两种 Loader

loaderclientLoader 可以一起使用。loader 将用于服务器端的初始 SSR(或预渲染),而 clientLoader 将用于随后的客户端导航。

// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
import { fakeDb } from "../db";

export async function loader({ params }: Route.LoaderArgs) {
  return fakeDb.getProduct(params.pid);
}

export async function clientLoader({
  serverLoader,
  params,
}: Route.ClientLoaderArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  const serverData = await serverLoader();
  return { ...serverData, ...res.json() };
}

export default function Product({
  loaderData,
}: Route.ComponentProps) {
  const { name, description } = loaderData;

  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

你还可以通过在函数上设置 hydrate 属性,强制客户端 loader 在注水(hydration)期间和页面渲染之前运行。在这种情况下,你将需要渲染一个 HydrateFallback 组件,以便在客户端 loader 运行时显示一个后备 UI。

export async function loader() {
  /* ... */
}

export async function clientLoader() {
  /* ... */
}

// force the client loader to run during hydration
clientLoader.hydrate = true as const; // `as const` for type inference

export function HydrateFallback() {
  return <div>Loading...</div>;
}

export default function Product() {
  /* ... */
}

下一篇:操作

另请参阅

文档和示例 CC 4.0