路由
本页内容

路由

配置路由

路由在 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 包提供了一种文件系统路由约定。

路由模块

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

路由模块具有更多功能,例如操作、标头和错误边界,但这些将在下一份指南中介绍:路由模块

嵌套路由

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

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;

索引路由

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 }
}

可选片段

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

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

您也可以拥有可选的静态片段

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

通配符

也称为“通配符”和“星号”片段。如果路由路径模式以/*结尾,则它将匹配/后面的任何字符,包括其他/字符。

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>
  );
}

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


下一步:路由模块

文档和示例 CC 4.0