路由
本頁面內容

路由

設定路由

路由在 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>
  );
}

請注意,這些路由不參與資料載入、動作、程式碼分割或任何其他路由模組功能,因此它們的使用案例比路由模組的使用案例更受限制。


下一步:路由模組

文件與範例 CC 4.0