React Router v7 已發佈。 查看文件
路由檔案命名
本頁內容

路由檔案命名

雖然您可以使用「routes」外掛選項配置路由,但大多數路由都是使用此檔案系統慣例建立的。新增一個檔案,就會得到一個路由。

請注意,您可以使用 .js.jsx.ts.tsx 副檔名。在範例中,我們將堅持使用 .tsx,以避免重複。

Dilum Sanjaya 製作了一個很棒的可視化工具,說明檔案系統中的路由如何對應到應用程式中的 URL,這可能有助於您了解這些慣例。

免責聲明

不過,在我們深入研究 Remix 慣例之前,我們想指出,基於檔案的路由是一個極具主觀性的想法。有些人喜歡「扁平」路由的想法,有些人討厭它,並且更喜歡將路由巢狀在資料夾中。有些人只是討厭基於檔案的路由,並且更喜歡透過 JSON 配置路由。有些人則更喜歡像在他們的 React Router SPA 中那樣透過 JSX 配置路由。

重點是,我們很清楚這一點,而且從一開始,Remix 就一直提供一流的方式,讓您可以透過routes/ignoredRouteFiles選擇退出,並手動配置您的路由。但是,必須有一些預設值,以便使用者可以快速輕鬆地開始使用 - 我們認為,下面的扁平路由慣例文件對於中小型應用程式來說是一個相當不錯的預設值,並且可以很好地擴展。

無論您使用哪種慣例,具有數百或數千個路由的大型應用程式總是會有些混亂 - 這個想法是透過 routes 配置,您可以建構出最適合您的應用程式/團隊的慣例。Remix 要有一個讓每個人都滿意的預設慣例,這幾乎是不可能的。我們寧願為您提供一個相當簡單的預設值,然後讓社群建立您可以從中選擇的任意數量的慣例。

因此,在我們深入探討 Remix 預設慣例的細節之前,如果您認為我們的預設不合您的口味,這裡有一些社群替代方案可供您查看。

  • remix-flat-routes - Remix 預設基本上是此套件的簡化版本。作者持續迭代和發展此套件,因此如果您大致上喜歡「扁平路由」的想法,但想要更多的功能(包括檔案和資料夾的混合方法),絕對要查看這個套件。
  • remix-custom-routes - 如果您想要更多的自訂功能,此套件可讓您定義哪些類型的檔案應被視為路由。這可讓您超越簡單的扁平/巢狀概念,並執行諸如「任何副檔名為 .route.tsx 的檔案都是路由」之類的操作。
  • remix-json-routes - 如果您只想透過設定檔指定路由,這就是您的首選 - 只需為 Remix 提供一個包含路由的 JSON 物件,並完全跳過扁平/巢狀概念。其中甚至還有一個 JSX 選項。

根路由

app/
├── routes/
└── root.tsx

app/root.tsx 中的檔案是您的根佈局,或稱為「根路由」(對於那些發音相同的人,非常抱歉!)。它的運作方式與所有其他路由相同,因此您可以匯出 loaderaction 等。

根路由通常看起來像這樣。它作為整個應用程式的根佈局,所有其他路由都將在 <Outlet /> 內呈現。

import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";

export default function Root() {
  return (
    <html lang="en">
      <head>
        <Links />
        <Meta />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

基本路由

app/routes 目錄中的任何 JavaScript 或 TypeScript 檔案都將成為您應用程式中的路由。檔案名稱對應到路由的 URL 路徑名稱,但 _index.tsx 例外,它是索引路由,用於 根路由

app/
├── routes/
│   ├── _index.tsx
│   └── about.tsx
└── root.tsx
URL 匹配的路由
/ app/routes/_index.tsx
/about app/routes/about.tsx

請注意,由於巢狀路由,這些路由將在 app/root.tsx 的 outlet 中呈現。

點分隔符

在路由檔案名稱中加入 . 將在 URL 中建立 /

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.trending.tsx
│   ├── concerts.salt-lake-city.tsx
│   └── concerts.san-diego.tsx
└── root.tsx
URL 匹配的路由
/ app/routes/_index.tsx
/about app/routes/about.tsx
/concerts/trending app/routes/concerts.trending.tsx
/concerts/salt-lake-city app/routes/concerts.salt-lake-city.tsx
/concerts/san-diego app/routes/concerts.san-diego.tsx

點分隔符也會建立巢狀結構,請參閱巢狀結構章節以取得更多資訊。

動態區段

通常您的 URL 不是靜態的,而是由資料驅動的。動態區段允許您匹配 URL 的區段,並在您的程式碼中使用該值。您可以使用 $ 字首來建立它們。

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.$city.tsx
│   └── concerts.trending.tsx
└── root.tsx
URL 匹配的路由
/ app/routes/_index.tsx
/about app/routes/about.tsx
/concerts/trending app/routes/concerts.trending.tsx
/concerts/salt-lake-city app/routes/concerts.$city.tsx
/concerts/san-diego app/routes/concerts.$city.tsx

Remix 將從 URL 解析該值,並將其傳遞給各種 API。我們將這些值稱為「URL 參數」。最常存取 URL 參數的地方是 loadersactions

export async function loader({
  params,
}: LoaderFunctionArgs) {
  return fakeDb.getAllConcertsForCity(params.city);
}

您會注意到 params 物件上的屬性名稱直接對應到您的檔案名稱:$city.tsx 會變成 params.city

路由可以有多個動態區段,例如 concerts.$city.$date,兩者都可以透過名稱在 params 物件上存取。

export async function loader({
  params,
}: LoaderFunctionArgs) {
  return fake.db.getConcerts({
    date: params.date,
    city: params.city,
  });
}

請參閱路由指南以取得更多資訊。

巢狀路由

巢狀路由是指將 URL 的區段與元件階層和資料耦合在一起的概念。您可以在路由指南中閱讀更多相關資訊。

您可以使用點分隔符建立巢狀路由。如果 . 之前的檔案名稱與另一個路由檔案名稱相符,則它會自動成為相符父路由的子路由。考慮以下路由

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts._index.tsx
│   ├── concerts.$city.tsx
│   ├── concerts.trending.tsx
│   └── concerts.tsx
└── root.tsx

所有以 app/routes/concerts. 開頭的路由都將是 app/routes/concerts.tsx 的子路由,並在父路由的 outlet_component 內呈現。

URL 匹配的路由 佈局
/ app/routes/_index.tsx app/root.tsx
/about app/routes/about.tsx app/root.tsx
/concerts app/routes/concerts._index.tsx app/routes/concerts.tsx
/concerts/trending app/routes/concerts.trending.tsx app/routes/concerts.tsx
/concerts/salt-lake-city app/routes/concerts.$city.tsx app/routes/concerts.tsx

請注意,當您加入巢狀路由時,通常會想要加入索引路由,以便當使用者直接訪問父 URL 時,在父路由的 outlet 內呈現某些內容。

例如,如果 URL 是 /concerts/salt-lake-city,則 UI 階層將如下所示

<Root>
  <Concerts>
    <City />
  </Concerts>
</Root>

沒有佈局巢狀結構的巢狀 URL

有時您希望 URL 是巢狀的,但您不想要自動佈局巢狀結構。您可以使用父區段上的尾隨底線來選擇不使用巢狀結構

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.$city.tsx
│   ├── concerts.trending.tsx
│   ├── concerts.tsx
│   └── concerts_.mine.tsx
└── root.tsx
URL 匹配的路由 佈局
/ app/routes/_index.tsx app/root.tsx
/about app/routes/about.tsx app/root.tsx
/concerts/mine app/routes/concerts_.mine.tsx app/root.tsx
/concerts/trending app/routes/concerts.trending.tsx app/routes/concerts.tsx
/concerts/salt-lake-city app/routes/concerts.$city.tsx app/routes/concerts.tsx

請注意,/concerts/mine 不再與 app/routes/concerts.tsx 巢狀結構,而是與 app/root.tsx 巢狀結構。trailing_ 底線建立一個路徑區段,但它不會建立佈局巢狀結構。

trailing_ 底線視為您父簽名的末端長度,它會將您從遺囑中刪除,並從佈局巢狀結構中移除後續的區段。

沒有巢狀 URL 的巢狀佈局

我們稱這些為無路徑路由

有時您希望與一組路由共用一個佈局,而不在 URL 中加入任何路徑區段。一個常見的範例是一組具有與公用頁面或已登入應用程式體驗不同的頁首/頁尾的驗證路由。您可以使用 _leading 底線來執行此操作。

 app/
├── routes/
│   ├── _auth.login.tsx
│   ├── _auth.register.tsx
│   ├── _auth.tsx
│   ├── _index.tsx
│   ├── concerts.$city.tsx
│   └── concerts.tsx
└── root.tsx
URL 匹配的路由 佈局
/ app/routes/_index.tsx app/root.tsx
/login app/routes/_auth.login.tsx app/routes/_auth.tsx
/register app/routes/_auth.register.tsx app/routes/_auth.tsx
/concerts app/routes/concerts.tsx app/root.tsx
/concerts/salt-lake-city app/routes/concerts.$city.tsx app/routes/concerts.tsx

_leading 底線視為您正在拉到檔案名稱上的毛毯,將檔案名稱從 URL 中隱藏起來。

可選區段

將路由區段包在括號中將使該區段成為可選的。

 app/
├── routes/
│   ├── ($lang)._index.tsx
│   ├── ($lang).$productId.tsx
│   └── ($lang).categories.tsx
└── root.tsx
URL 匹配的路由
/ app/routes/($lang)._index.tsx
/categories app/routes/($lang).categories.tsx
/en/categories app/routes/($lang).categories.tsx
/fr/categories app/routes/($lang).categories.tsx
/american-flag-speedo app/routes/($lang)._index.tsx
/en/american-flag-speedo app/routes/($lang).$productId.tsx
/fr/american-flag-speedo app/routes/($lang).$productId.tsx

您可能會想知道為什麼 /american-flag-speedo 符合 ($lang)._index.tsx 路由,而不是 ($lang).$productId.tsx。這是因為當您有一個可選的動態參數區段,後跟另一個動態參數時,Remix 無法可靠地判斷單區段 URL(例如 /american-flag-speedo)是否應該匹配 /:lang /:productId。可選區段會積極匹配,因此它會匹配 /:lang。如果您有這種設定,建議在 ($lang)._index.tsx loader 中查看 params.lang,如果 params.lang 不是有效的語言代碼,則重新導向至目前/預設語言的 /:lang/american-flag-speedo

Splat 路由

雖然動態區段匹配單一路徑區段(URL 中兩個 / 之間的東西),但 splat 路由將匹配 URL 的其餘部分,包括斜線。

 app/
├── routes/
│   ├── _index.tsx
│   ├── $.tsx
│   ├── about.tsx
│   └── files.$.tsx
└── root.tsx
URL 匹配的路由
/ app/routes/_index.tsx
/about app/routes/about.tsx
/beef/and/cheese app/routes/$.tsx
/files app/routes/files.$.tsx
/files/talks/remix-conf_old.pdf app/routes/files.$.tsx
/files/talks/remix-conf_final.pdf app/routes/files.$.tsx
/files/talks/remix-conf-FINAL-MAY_2022.pdf app/routes/files.$.tsx

與動態路由參數類似,您可以使用 "*" 鍵,在 splat 路由的 params 上存取匹配路徑的值。

export async function loader({
  params,
}: LoaderFunctionArgs) {
  const filePath = params["*"];
  return fake.getFileInfo(filePath);
}

逸出特殊字元

如果您希望 Remix 用於這些路由慣例的其中一個特殊字元實際上是 URL 的一部分,您可以使用 [] 字元來逸出這些慣例。

檔案名稱 URL
app/routes/sitemap[.]xml.tsx /sitemap.xml
app/routes/[sitemap.xml].tsx /sitemap.xml
app/routes/weird-url.[_index].tsx /weird-url/_index
app/routes/dolla-bills-[$].tsx /dolla-bills-$
app/routes/[[so-weird]].tsx /[so-weird]

用於組織的資料夾

路由也可以是資料夾,內部有一個 route.tsx 檔案定義路由模組。資料夾中的其餘檔案不會成為路由。這允許您將程式碼組織得更靠近使用它們的路由,而不是在其他資料夾中重複功能名稱。

資料夾內的檔案對於路由路徑沒有任何意義,路由路徑完全由資料夾名稱定義

考慮以下路由

 app/
├── routes/
│   ├── _landing._index.tsx
│   ├── _landing.about.tsx
│   ├── _landing.tsx
│   ├── app._index.tsx
│   ├── app.projects.tsx
│   ├── app.tsx
│   └── app_.projects.$id.roadmap.tsx
└── root.tsx

它們中的一些或全部都可以是資料夾,在其中包含自己的 route 模組。

app/
├── routes/
│   ├── _landing._index/
│   │   ├── route.tsx
│   │   └── scroll-experience.tsx
│   ├── _landing.about/
│   │   ├── employee-profile-card.tsx
│   │   ├── get-employee-data.server.ts
│   │   ├── route.tsx
│   │   └── team-photo.jpg
│   ├── _landing/
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   └── route.tsx
│   ├── app._index/
│   │   ├── route.tsx
│   │   └── stats.tsx
│   ├── app.projects/
│   │   ├── get-projects.server.ts
│   │   ├── project-buttons.tsx
│   │   ├── project-card.tsx
│   │   └── route.tsx
│   ├── app/
│   │   ├── footer.tsx
│   │   ├── primary-nav.tsx
│   │   └── route.tsx
│   ├── app_.projects.$id.roadmap/
│   │   ├── chart.tsx
│   │   ├── route.tsx
│   │   └── update-timeline.server.ts
│   └── contact-us.tsx
└── root.tsx

請注意,當您將路由模組轉換為資料夾時,路由模組將變成 folder/route.tsx,資料夾中的所有其他模組都不會成為路由。例如

# these are the same route:
app/routes/app.tsx
app/routes/app/route.tsx

# as are these
app/routes/app._index.tsx
app/routes/app._index/route.tsx

擴展

我們對於擴展的一般建議是讓每個路由都成為一個資料夾,並將該路由專用的模組放在資料夾中,然後將共享模組放在路由資料夾外部的其他位置。這有幾個好處

  • 容易識別共享模組,因此在變更它們時請謹慎處理
  • 容易組織和重構特定路由的模組,而不會產生「檔案組織疲勞」並使應用程式的其他部分混亂
文件和範例授權使用 MIT