跳转到内容

Route File Naming

路由文件命名

虽然您可以通过 routes插件选项 配置路由,但大多数路由都是使用此文件系统约定创建的。添加文件,获取路由。

请注意,您可以使用 .js.jsx.ts.tsx 文件扩展名。为了避免重复,我们将在示例中使用 .tsx

Dilum Sanjaya 制作了 一个很棒的可视化效果,展示了文件系统中的路由如何映射到应用程序中的 URL,这可能有助于您理解这些约定。

免责声明

不过,在深入探讨 Remix 惯例之前,我们想指出,基于文件的路由是一个极其主观的想法。有些人喜欢扁平路由的想法,有些人讨厌它,更喜欢将路由嵌套在文件夹中。有些人只是讨厌基于文件的路由,更喜欢通过 JSON 配置路由。有些人更喜欢通过 JSX 配置路由,就像他们在 React Router SPA 中所做的那样。

重点是,我们非常清楚这一点,从一开始,Remix 就一直为您提供一流的退出方式,即通过 routes/ignoredRouteFiles手动配置您的路由。但是,必须有一些默认设置,以便人们可以快速轻松地启动和运行 - 我们认为下面的扁平路由约定文档是一个非常好的默认设置,可以很好地扩展到中小型应用程序。

无论您使用哪种约定,拥有数百或数千条路由的大型应用程序总是会有点混乱 - 而我们的想法是通过路由配置,您可以构建最适合您的应用程序/团队的约定。对于 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
URLMatched Routes
/app/routes/_index.tsx
/aboutapp/routes/about.tsx

请注意,由于嵌套路由,这些路由将在app/root.tsx的出口中呈现。

点分隔符

在路由文件名中添加将在 URL 中创建/

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.trending.tsx
│ ├── concerts.salt-lake-city.tsx
│ └── concerts.san-diego.tsx
└── root.tsx
URLMatched Route
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/concerts/trendingapp/routes/concerts.trending.tsx
/concerts/salt-lake-cityapp/routes/concerts.salt-lake-city.tsx
/concerts/san-diegoapp/routes/concerts.san-diego.tsx

点分隔符也会创建嵌套,有关更多信息,请参阅嵌套部分

动态细分

通常,您的 URL 不是静态的,而是由数据驱动的。动态段允许您匹配 URL 的段并在代码中使用该值。您可以使用 $ 前缀创建它们。

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.$city.tsx
│ └── concerts.trending.tsx
└── root.tsx
URLMatched Route
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/concerts/trendingapp/routes/concerts.trending.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsx
/concerts/san-diegoapp/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 内呈现。

URLMatched RouteLayout
/app/routes/_index.tsxapp/root.tsx
/aboutapp/routes/about.tsxapp/root.tsx
/concertsapp/routes/concerts._index.tsxapp/routes/concerts.tsx
/concerts/trendingapp/routes/concerts.trending.tsxapp/routes/concerts.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/routes/concerts.tsx

请注意,您通常需要在添加嵌套路由时添加索引路由,以便当用户直接访问父级 URL 时,某些内容会在父级出口内呈现。

例如,如果 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
URLMatched RouteLayout
/app/routes/_index.tsxapp/root.tsx
/aboutapp/routes/about.tsxapp/root.tsx
/concerts/mineapp/routes/concerts_.mine.tsxapp/root.tsx
/concerts/trendingapp/routes/concerts.trending.tsxapp/routes/concerts.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/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
URLMatched RouteLayout
/app/routes/_index.tsxapp/root.tsx
/loginapp/routes/_auth.login.tsxapp/routes/_auth.tsx
/registerapp/routes/_auth.register.tsxapp/routes/_auth.tsx
/concertsapp/routes/concerts.tsxapp/root.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/routes/concerts.tsx

可以将 _leading 下划线想象成一条盖在文件名上的毯子,将文件名从 URL 中隐藏起来。

可选段

将路由段括在括号中将使该段成为可选的。

app/
├── routes/
│ ├── ($lang)._index.tsx
│ ├── ($lang).$productId.tsx
│ └── ($lang).categories.tsx
└── root.tsx
URLMatched Route
/app/routes/($lang)._index.tsx
/categoriesapp/routes/($lang).categories.tsx
/en/categoriesapp/routes/($lang).categories.tsx
/fr/categoriesapp/routes/($lang).categories.tsx
/american-flag-speedoapp/routes/($lang)._index.tsx
/en/american-flag-speedoapp/routes/($lang).$productId.tsx
/fr/american-flag-speedoapp/routes/($lang).$productId.tsx

您可能想知道为什么 /american-flag-speedo($lang)._index.tsx 路由匹配,而不是 ($lang).$productId.tsx。这是因为当您有一个可选的动态参数段后跟另一个动态参数时,Remix 无法可靠地确定单段 URL(例如 /american-flag-speedo)是否应匹配 /:lang /:productId。可选段会积极匹配,因此它将匹配 /:lang。如果您有这种类型的设置,建议查看 ($lang)._index.tsx 加载器中的 params.lang,如果 params.lang 不是有效的语言代码,则重定向到当前/默认语言的 /:lang/american-flag-speedo

Splat 路由

虽然 动态段 匹配单个路径段(URL 中两个 / 之间的内容),但 splat 路由将匹配 URL 的其余部分,包括斜杠。

app/
├── routes/
│ ├── _index.tsx
│ ├── $.tsx
│ ├── about.tsx
│ └── files.$.tsx
└── root.tsx
URLMatched Route
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/beef/and/cheeseapp/routes/$.tsx
/filesapp/routes/files.$.tsx
/files/talks/remix-conf_old.pdfapp/routes/files.$.tsx
/files/talks/remix-conf_final.pdfapp/routes/files.$.tsx
/files/talks/remix-conf-FINAL-MAY_2022.pdfapp/routes/files.$.tsx

与动态路由参数类似,您可以使用 "*" 键访问 splat 路由的 params 上匹配路径的值。

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

转义特殊字符

如果您希望 Remix 用于这些路由约定的特殊字符之一实际上成为 URL 的一部分,则可以使用 [] 字符转义这些约定。

FilenameURL
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

它们中的一些或者全部都可以是包含其自己的路由模块的文件夹。

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

缩放

我们对扩展的一般建议是将每条路由都设为一个文件夹,并将该路由专用的模块放在该文件夹中,然后将共享模块放在路由文件夹之外的其他位置。这有几个好处:

  • 易于识别共享模块,因此在更改它们时要小心谨慎
  • 易于组织和重构特定路由的模块,而不会造成文件组织疲劳并使应用程序的其他部分变得混乱