路由在 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>;
}
路由模組具有更多功能,例如動作、標頭和錯誤邊界,但它們將在下一份指南中介紹:路由模組
路由可以巢狀於父路由內。
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>
);
}
請注意,這些路由不參與資料載入、動作、程式碼分割或任何其他路由模組功能,因此它們的使用案例比路由模組的使用案例更受限制。
下一步:路由模組