虽然你可以通过 "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 参数最有用的地方是在 加载程序 和 操作 中。
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 无法可靠地确定诸如/american-flag-speedo
的单段 URL 是否应该匹配/:lang
/:productId
。可选段会按顺序匹配,因此它将匹配/:lang
。如果您有这种设置,建议您查看($lang)._index.tsx
加载器中的params.lang
,如果params.lang
不是有效的语言代码,则重定向到/:lang/american-flag-speedo
以获取当前/默认语言。
虽然 动态段匹配单个路径段(URL 中两个/
之间的内容),但散列路由将匹配 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 |
类似于动态路由参数,您可以使用"*"
键在散列路由的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
我们对扩展的一般建议是将每个路由都设置为文件夹,并将专门由该路由使用的模块放入该文件夹,然后将共享模块放在路由文件夹之外的其他位置。这有几个好处