路由
本页内容

路由

配置路由

路由在 app/routes.ts 中配置。每个路由有两个必需部分:用于匹配 URL 的 URL 模式,以及定义其行为的路由模块的文件路径。

import {
  type RouteConfig,
  route,
} from "@react-router/dev/routes";

export default [
  route("some/path", "./some/file.tsx"),
  // pattern ^           ^ module file
] satisfies RouteConfig;

这是一个更大的路由配置示例

import {
  type RouteConfig,
  route,
  index,
  layout,
  prefix,
} from "@react-router/dev/routes";

export default [
  index("./home.tsx"),
  route("about", "./about.tsx"),

  layout("./auth/layout.tsx", [
    route("login", "./auth/login.tsx"),
    route("register", "./auth/register.tsx"),
  ]),

  ...prefix("concerts", [
    index("./concerts/home.tsx"),
    route(":city", "./concerts/city.tsx"),
    route("trending", "./concerts/trending.tsx"),
  ]),
] satisfies RouteConfig;

如果您喜欢通过文件命名约定而不是配置来定义路由,则 @react-router/fs-routes 包提供了文件系统路由约定。您甚至可以根据需要组合不同的路由约定

import {
  type RouteConfig,
  route,
} from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default [
  route("/", "./home.tsx"),

  ...(await flatRoutes()),
] satisfies RouteConfig;

路由模块

routes.ts 中引用的文件定义了每个路由的行为

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

这是一个路由模块示例

// provides type safety/inference
import type { Route } from "./+types/team";

// provides `loaderData` to the component
export async function loader({ params }: Route.LoaderArgs) {
  let team = await fetchTeam(params.teamId);
  return { name: team.name };
}

// renders after the loader is done
export default function Component({
  loaderData,
}: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}

路由模块具有更多功能,如 actions、headers 和错误边界,但它们将在下一篇指南中介绍:路由模块

嵌套路由

路由可以嵌套在父路由内。

import {
  type RouteConfig,
  route,
  index,
} from "@react-router/dev/routes";

export default [
  // parent route
  route("dashboard", "./dashboard.tsx", [
    // child routes
    index("./home.tsx"),
    route("settings", "./settings.tsx"),
  ]),
] satisfies RouteConfig;

父路由的路径自动包含在子路由中,因此此配置创建了 "/dashboard""/dashboard/settings" URL。

子路由通过父路由中的 <Outlet/> 渲染。

import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* will either be home.tsx or settings.tsx */}
      <Outlet />
    </div>
  );
}

根路由

routes.ts 中的每个路由都嵌套在特殊的 app/root.tsx 模块内。

布局路由

使用 layout,布局路由为其子路由创建新的嵌套,但它们不会向 URL 添加任何片段。它类似于根路由,但它们可以在任何级别添加。

import {
  type RouteConfig,
  route,
  layout,
  index,
  prefix,
} from "@react-router/dev/routes";

export default [
  layout("./marketing/layout.tsx", [
    index("./marketing/home.tsx"),
    route("contact", "./marketing/contact.tsx"),
  ]),
  ...prefix("projects", [
    index("./projects/home.tsx"),
    layout("./projects/project-layout.tsx", [
      route(":pid", "./projects/project.tsx"),
      route(":pid/edit", "./projects/edit-project.tsx"),
    ]),
  ]),
] satisfies RouteConfig;

要使 projects/home.tsx 出现在布局中,我们需要一个 outlet

import { Outlet } from "react-router";

export default function ProjectLayout() {
  return (
    <div>
      <aside>Example sidebar</aside>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

索引路由

index(componentFile),

索引路由在其父路由的 URL 处渲染到其父路由的 Outlet 中(类似于默认子路由)。

import {
  type RouteConfig,
  route,
  index,
} from "@react-router/dev/routes";

export default [
  // renders into the root.tsx Outlet at /
  index("./home.tsx"),
  route("dashboard", "./dashboard.tsx", [
    // renders into the dashboard.tsx Outlet at /dashboard
    index("./dashboard-home.tsx"),
    route("settings", "./dashboard-settings.tsx"),
  ]),
] satisfies RouteConfig;

请注意,索引路由不能有子路由。

路由前缀

使用 prefix,您可以为一组路由添加路径前缀,而无需引入父路由文件。

import {
  type RouteConfig,
  route,
  layout,
  index,
  prefix,
} from "@react-router/dev/routes";

export default [
  layout("./marketing/layout.tsx", [
    index("./marketing/home.tsx"),
    route("contact", "./marketing/contact.tsx"),
  ]),
  ...prefix("projects", [
    index("./projects/home.tsx"),
    layout("./projects/project-layout.tsx", [
      route(":pid", "./projects/project.tsx"),
      route(":pid/edit", "./projects/edit-project.tsx"),
    ]),
  ]),
] satisfies RouteConfig;

动态段

如果路径段以 : 开头,则它将成为“动态段”。当路由匹配 URL 时,动态段将从 URL 中解析出来,并作为 params 提供给其他路由器 API。

route("teams/:teamId", "./team.tsx"),
import type { Route } from "./+types/team";

export async function loader({ params }: Route.LoaderArgs) {
  //                           ^? { teamId: string }
}

export default function Component({
  params,
}: Route.ComponentProps) {
  params.teamId;
  //        ^ string
}

在一个路由路径中可以有多个动态段

route("c/:categoryId/p/:productId", "./product.tsx"),
import type { Route } from "./+types/product";

async function loader({ params }: LoaderArgs) {
  //                    ^? { categoryId: string; productId: string }
}

您应确保给定路径中的所有动态段都是唯一的。否则,当 params 对象被填充时 - 后面的动态段值将覆盖较早的值。

可选段

您可以通过在段末尾添加 ? 来使路由段成为可选的。

route(":lang?/categories", "./categories.tsx"),

您也可以有可选的静态段

route("users/:userId/edit?", "./user.tsx");

通配符

也称为 “catchall” 和 “star” 段。如果路由路径模式以 /* 结尾,则它将匹配 / 之后的所有字符,包括其他 / 字符。

route("files/*", "./files.tsx"),
export async function loader({ params }: Route.LoaderArgs) {
  // params["*"] will contain the remaining URL after files/
}

您可以解构 *,只需为其分配一个新名称即可。一个常用的名称是 splat

const { "*": splat } = params;

组件路由

您还可以使用与 URL 匹配的组件来匹配组件树中任何位置的元素

import { Routes, Route } from "react-router";

function Wizard() {
  return (
    <div>
      <h1>Some Wizard with Steps</h1>
      <Routes>
        <Route index element={<StepOne />} />
        <Route path="step-2" element={<StepTwo />} />
        <Route path="step-3" element={<StepThree />} />
      </Routes>
    </div>
  );
}

请注意,这些路由不参与数据加载、actions、代码分割或任何其他路由模块功能,因此它们的使用场景比路由模块的使用场景更有限。


下一步:路由模块

文档和示例 CC 4.0