Router config

490 阅读4分钟

路由配置

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

以下是导入相关内容并配置简单路由的示例代码:

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

export default [
  route("some/path", "./some/file.tsx"),
  // 模式    ^           ^ 模块文件
] 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"),
//           路由模块  ^^^^^^^^

以下是一个路由模块的示例代码:

// 提供类型安全/类型推断
import type { Route } from "./+types/team";

// 向组件提供 `loaderData`
export async function loader({ params }: Route.LoaderArgs) {
  let team = await fetchTeam(params.teamId);
  return { name: team.name };
}

// 在加载器完成后进行渲染
export default function Component({
  loaderData,
}: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}

路由模块还有更多功能,比如操作、头部信息以及错误边界等内容,这些将会在下一篇指南(《路由模块》)中介绍。

嵌套路由

路由可以嵌套在父路由内部,示例如下:

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

export default [
  // 父路由
  route("dashboard", "./dashboard.tsx", [
    // 子路由
    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>
      {/* 将会渲染 home.tsx 或者 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 [
  // 在根路由(/)对应的 root.tsx 的 Outlet 中渲染
  index("./home.tsx"),
  route("dashboard", "./dashboard.tsx", [
    // 在 /dashboard 对应的 dashboard.tsx 的 Outlet 中渲染
    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 中解析出来,并作为参数提供给其他路由相关的 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");

通配符片段(Splats)

也被称为 “catchall”(捕获所有)和 “star”(星号)片段。如果路由路径模式以 /* 结尾,那么它将匹配 / 之后的任意字符,包括其他 / 字符,示例如下:

route("files/*", "./files.tsx"),

使用时:

export async function loader({ params }: Route.LoaderArgs) {
  // params["*"] 将包含 files/ 之后剩余的URL内容
}

可以解构这个通配符,只是需要给它赋一个新名字,常见的名字是 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>
  );
}

需要注意的是,这些路由并不参与数据加载、操作、代码拆分或其他任何路由模块的功能,所以它们的使用场景相较于路由模块来说更有限。

你能为我提供一个具体的路由配置示例吗?

除了 URL 模式和文件路径,路由还可以配置哪些其他属性?

如何在 React Router 中处理嵌套路由?敬请期待。。。。