雖然您可以使用「routes」外掛選項配置路由,但大多數路由都是使用此檔案系統慣例建立的。新增一個檔案,就會得到一個路由。
請注意,您可以使用 .js
、.jsx
、.ts
或 .tsx
副檔名。在範例中,我們將堅持使用 .tsx
,以避免重複。
不過,在我們深入研究 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
中的檔案是您的根佈局,或稱為「根路由」(對於那些發音相同的人,非常抱歉!)。它的運作方式與所有其他路由相同,因此您可以匯出 loader
、action
等。
根路由通常看起來像這樣。它作為整個應用程式的根佈局,所有其他路由都將在 <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 參數的地方是 loaders 和 actions。
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 是巢狀的,但您不想要自動佈局巢狀結構。您可以使用父區段上的尾隨底線來選擇不使用巢狀結構
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 中加入任何路徑區段。一個常見的範例是一組具有與公用頁面或已登入應用程式體驗不同的頁首/頁尾的驗證路由。您可以使用 _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
。
雖然動態區段匹配單一路徑區段(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
我們對於擴展的一般建議是讓每個路由都成為一個資料夾,並將該路由專用的模組放在資料夾中,然後將共享模組放在路由資料夾外部的其他位置。這有幾個好處