@react-router/fs-routes
包启用了基于文件约定的路由配置。
首先安装 @react-router/fs-routes
包
npm i @react-router/fs-routes
然后在您的 app/routes.ts
文件中使用它来提供路由配置
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;
app/routes
目录中的任何模块都将默认成为应用程序中的路由。ignoredRouteFiles
选项允许您指定不应作为路由包含的文件
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes({
ignoredRouteFiles: ["home.tsx"],
}) satisfies RouteConfig;
默认情况下,它会在 app/routes
目录中查找路由,但这可以通过 rootDirectory
选项进行配置,该选项相对于您的应用程序目录
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes({
rootDirectory: "file-routes",
}) satisfies RouteConfig;
本指南的其余部分将假设您正在使用默认的 app/routes
目录。
文件名映射到路由的 URL 路径名,但 _index.tsx
除外,它是根路由的索引路由。您可以使用 .js
、.jsx
、.ts
或 .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 |
该值将从 URL 中解析并传递给各种 API。我们将这些值称为“URL 参数”。访问 URL 参数最有用处的地方是在加载器和操作中。
export async function loader({ params }) {
return fakeDb.getAllConcertsForCity(params.city);
}
您会注意到 params
对象上的属性名称直接映射到您的文件名:$city.tsx
变为 params.city
。
路由可以有多个动态段,如 concerts.$city.$date
,两者都通过名称在 params 对象上访问
export async function loader({ params }) {
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 内呈现。
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/routes/concerts.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
。这是因为当您有一个可选的动态参数段后跟另一个动态参数时,无法可靠地确定像 /american-flag-speedo
这样的单段 URL 应该匹配 /:lang
还是 /:productId
。可选段会贪婪匹配,因此它会匹配 /:lang
。如果您有这种类型的设置,建议在 ($lang)._index.tsx
加载器中检查 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/react-conf_old.pdf |
app/routes/files.$.tsx |
/files/talks/react-conf_final.pdf |
app/routes/files.$.tsx |
/files/talks/react-conf-FINAL-MAY_2024.pdf |
app/routes/files.$.tsx |
与动态路由参数类似,您可以使用 "*"
键在 splat 路由的 params
上访问匹配路径的值。
export async function loader({ params }) {
const filePath = params["*"];
return fake.getFileInfo(filePath);
}
要创建一个将匹配任何不匹配其他已定义路由的请求的路由(例如 404 页面),请在您的路由目录中创建一个名为 $.tsx
的文件
URL | 匹配的路由 |
---|---|
/ |
app/routes/_index.tsx |
/about |
app/routes/about.tsx |
/any-invalid-path-will-match |
app/routes/$.tsx |
默认情况下,匹配的路由将返回 200 响应,因此请务必修改您的捕获所有路由以返回 404
export async function loader() {
return data({}, 404);
}
如果您希望用于这些路由约定的特殊字符之一实际成为 URL 的一部分,您可以使用 []
字符来转义这些约定。这对于在 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] |
app/routes/reports.$id[.pdf].ts |
/reports/123.pdf |
路由也可以是文件夹,其中包含一个定义路由模块的 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