路由在 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;
请注意
home.tsx
和 contact.tsx
将被渲染到 marketing/layout.tsx
的 outlet 中,而不会创建任何新的 URL 路径。project.tsx
和 edit-project.tsx
将在 /projects/:pid
和 /projects/:pid/edit
路径下被渲染到 projects/project-layout.tsx
的 outlet 中,而 projects/home.tsx
不会。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");
也称为“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;
你还可以使用 splat 来捕获不匹配任何路由的请求。
route("*", "./catchall.tsx"); // catchall route,
export function loader() {
throw new Response("Page not found", { status: 404 });
}
你也可以使用组件,将 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>
);
}
请注意,这些路由不参与数据加载、操作、代码分割或任何其他路由模块功能,因此它们的用例比路由模块更有限。
下一步:路由模块