路由模块
本页内容

路由模块

简介

routes.ts 中引用的文件称为路由模块。

route("teams/:teamId", "./team.tsx"),
//           route module ^^^^^^^^

路由模块是 React Router 框架功能的基础,它们定义了

  • 自动代码分割
  • 数据加载
  • 操作
  • 重新验证
  • 错误边界
  • 等等

本指南是对每个路由模块功能的快速概述。入门指南的其余部分将更详细地介绍这些功能。

组件 (default)

路由模块中的 default 导出定义了路由匹配时将渲染的组件。

export default function MyRouteComponent() {
  return (
    <div>
      <h1>Look ma!</h1>
      <p>
        I'm still using React Router after like 10 years.
      </p>
    </div>
  );
}

传递给组件的 Props

当组件渲染时,它会收到在 Route.ComponentProps 中定义的 props,React Router 会自动为你生成这些 props。这些 props 包括

  1. loaderData:从此路由模块中的 loader 函数返回的数据
  2. actionData:从此路由模块中的 action 函数返回的数据
  3. params:包含路由参数的对象(如果有)。
  4. matches:当前路由树中所有匹配项的数组。

你可以使用这些 props 来替代像 useLoaderDatauseParams 这样的 hooks。这可能更可取,因为它们会自动为路由正确键入。

使用 props

import type { Route } from "./+types/route-name";

export default function MyRouteComponent({
  loaderData,
  actionData,
  params,
  matches,
}: Route.ComponentProps) {
  return (
    <div>
      <h1>Welcome to My Route with Props!</h1>
      <p>Loader Data: {JSON.stringify(loaderData)}</p>
      <p>Action Data: {JSON.stringify(actionData)}</p>
      <p>Route Parameters: {JSON.stringify(params)}</p>
      <p>Matched Routes: {JSON.stringify(matches)}</p>
    </div>
  );
}

unstable_middleware

路由中间件在服务器上按顺序在文档和数据请求之前和之后运行。这为你提供了一个单一的地方来执行日志记录、身份验证和响应后处理等操作。next 函数会继续向下执行链条,在叶子路由上,next 函数会执行导航的加载器/操作。

这是一个在服务器上记录请求的中间件示例

async function loggingMiddleware(
  { request, context },
  next,
) {
  console.log(
    `${new Date().toISOString()} ${request.method} ${request.url}`,
  );
  const start = performance.now();
  const response = await next();
  const duration = performance.now() - start;
  console.log(
    `${new Date().toISOString()} Response ${response.status} (${duration}ms)`,
  );
  return response;
}

export const unstable_middleware = [loggingMiddleware];

这是一个检查已登录用户并在 context 中设置用户的中间件示例,然后你可以从加载器中访问该用户

async function authMiddleware ({
  request,
  context,
}) => {
  const session = await getSession(request);
  const userId = session.get("userId");

  if (!userId) {
    throw redirect("/login");
  }

  const user = await getUserById(userId);
  context.set(userContext, user);
};

export const unstable_middleware = [authMiddleware];

请确保你理解中间件何时运行,以确保你的应用程序在向路由添加中间件时能按预期运行。

另请参阅

unstable_clientMiddleware

这是 unstable_middleware 的客户端等价物,在客户端导航期间在浏览器中运行。与服务器中间件的唯一区别是客户端中间件不返回响应,因为它们不是在服务器上包装 HTTP 请求。

这是一个在客户端上记录请求的中间件示例

async function loggingMiddleware(
  { request, context },
  next,
) {
  console.log(
    `${new Date().toISOString()} ${request.method} ${request.url}`,
  );
  const start = performance.now();
  await next(); // 👈 No Response returned
  const duration = performance.now() - start;
  console.log(
    `${new Date().toISOString()} Response ${response.status} (${duration}ms)`,
  );
  // ✅ No need to return anything
}

export const unstable_clientMiddleware = [
  loggingMiddleware,
];

另请参阅

loader

路由加载器在渲染之前为路由组件提供数据。它们仅在服务器渲染时或在预渲染的构建过程中在服务器上被调用。

export async function loader() {
  return { message: "Hello, world!" };
}

export default function MyRoute({ loaderData }) {
  return <h1>{loaderData.message}</h1>;
}

另请参阅

clientLoader

仅在浏览器中调用,路由客户端加载器为路由组件提供数据,以补充或替代路由加载器。

export async function clientLoader({ serverLoader }) {
  // call the server loader
  const serverData = await serverLoader();
  // And/or fetch data on the client
  const data = getDataFromClient();
  // Return the data to expose through useLoaderData()
  return data;
}

客户端加载器可以通过在函数上设置 hydrate 属性来参与服务器渲染页面的初始页面加载水合

export async function clientLoader() {
  // ...
}
clientLoader.hydrate = true as const;

通过使用 as const,TypeScript 将推断 clientLoader.hydrate 的类型为 true 而不是 boolean。这样,React Router 就可以根据 clientLoader.hydrate 的值推导出 loaderData 的类型。

另请参阅

action

路由操作允许服务器端数据突变,当从 <Form>useFetcheruseSubmit 调用时,会自动重新验证页面上的所有加载器数据。

// route("/list", "./list.tsx")
import { Form } from "react-router";
import { TodoList } from "~/components/TodoList";

// this data will be loaded after the action completes...
export async function loader() {
  const items = await fakeDb.getItems();
  return { items };
}

// ...so that the list here is updated automatically
export default function Items({ loaderData }) {
  return (
    <div>
      <List items={loaderData.items} />
      <Form method="post" navigate={false} action="/list">
        <input type="text" name="title" />
        <button type="submit">Create Todo</button>
      </Form>
    </div>
  );
}

export async function action({ request }) {
  const data = await request.formData();
  const todo = await fakeDb.addItem({
    title: data.get("title"),
  });
  return { ok: true };
}

另请参阅

clientAction

类似于路由操作,但仅在浏览器中调用。

export async function clientAction({ serverAction }) {
  fakeInvalidateClientSideCache();
  // can still call the server action if needed
  const data = await serverAction();
  return data;
}

另请参阅

ErrorBoundary

当其他路由模块 API 抛出错误时,路由模块的 ErrorBoundary 将代替路由组件进行渲染。

import {
  isRouteErrorResponse,
  useRouteError,
} from "react-router";

export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h1>
          {error.status} {error.statusText}
        </h1>
        <p>{error.data}</p>
      </div>
    );
  } else if (error instanceof Error) {
    return (
      <div>
        <h1>Error</h1>
        <p>{error.message}</p>
        <p>The stack trace is:</p>
        <pre>{error.stack}</pre>
      </div>
    );
  } else {
    return <h1>Unknown Error</h1>;
  }
}

另请参阅

HydrateFallback

在初始页面加载时,路由组件只有在客户端加载器完成后才会渲染。如果导出,HydrateFallback 可以立即渲染以替代路由组件。

export async function clientLoader() {
  const data = await fakeLoadLocalGameData();
  return data;
}

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

export default function Component({ loaderData }) {
  return <Game data={loaderData} />;
}

headers

路由 headers 函数定义了在服务器渲染时与响应一起发送的 HTTP 标头。

export function headers() {
  return {
    "X-Stretchy-Pants": "its for fun",
    "Cache-Control": "max-age=300, s-maxage=3600",
  };
}

另请参阅

handle

路由 handle 允许应用程序在 useMatches 中向路由匹配添加任何内容,以创建抽象(如面包屑等)。

export const handle = {
  its: "all yours",
};

另请参阅

路由 links 定义要在文档 <head> 中渲染的 <link> 元素

export function links() {
  return [
    {
      rel: "icon",
      href: "/favicon.png",
      type: "image/png",
    },
    {
      rel: "stylesheet",
      href: "https://example.com/some/styles.css",
    },
    {
      rel: "preload",
      href: "/images/banner.jpg",
      as: "image",
    },
  ];
}

所有路由链接都将被聚合并通过 <Links /> 组件渲染,通常在你的应用程序根目录中渲染

import { Links } from "react-router";

export default function Root() {
  return (
    <html>
      <head>
        <Links />
      </head>

      <body />
    </html>
  );
}

meta

路由 meta 定义要在 <Meta /> 组件中渲染的 meta 标签,通常放置在 <head> 中。

自 React 19 起,建议使用内置的 <meta> 元素,而不是使用路由模块的 meta 导出。

以下是如何使用它和 <title> 元素的示例

export default function MyRoute() {
  return (
    <div>
      <title>Very cool app</title>
      <meta property="og:title" content="Very cool app" />
      <meta
        name="description"
        content="This app is the best"
      />
      {/* The rest of your route content... */}
    </div>
  );
}
export function meta() {
  return [
    { title: "Very cool app" },
    {
      property: "og:title",
      content: "Very cool app",
    },
    {
      name: "description",
      content: "This app is the best",
    },
  ];
}
import { Meta } from "react-router";

export default function Root() {
  return (
    <html>
      <head>
        <Meta />
      </head>

      <body />
    </html>
  );
}

使用最后一个匹配路由的 meta,允许你覆盖父路由的 meta。需要注意的是,整个 meta 描述符数组被替换,而不是合并。这为你提供了在不同级别的页面之间构建自己的 meta 组合逻辑的灵活性。

另请参阅

shouldRevalidate

在框架模式下,路由加载器会在所有导航和表单提交后自动重新验证(这与数据模式不同)。这使得中间件和加载器能够共享请求上下文,并以与在数据模式中不同的方式进行优化。

定义此函数允许你为路由加载器选择退出导航和表单提交的重新验证。

import type { ShouldRevalidateFunctionArgs } from "react-router";

export function shouldRevalidate(
  arg: ShouldRevalidateFunctionArgs,
) {
  return true;
}

ShouldRevalidateFunctionArgs 参考文档 ↗


下一步:渲染策略

文档和示例 CC 4.0
编辑