文件路由约定
本页内容

文件路由约定

@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 的出口中呈现。

点分隔符

在路由文件名中添加 . 将在 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 serverLoader({ params }) {
  return fakeDb.getAllConcertsForCity(params.city);
}

你会注意到 params 对象上的属性名称直接映射到你的文件名名称: $city.tsx 变为 params.city

路由可以有多个动态片段,例如 concerts.$city.$date,两者都可以通过名称在 params 对象上访问

export async function serverLoader({ 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 的子路由,并在父路由的出口内呈现。

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 时,在父路由的出口内呈现某些内容。

例如,如果 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/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,并将 /:lang/american-flag-speedo 重定向到当前/默认语言(如果 params.lang 不是有效的语言代码)。

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/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 serverLoader({ params }) {
  const filePath = params["*"];
  return fake.getFileInfo(filePath);
}

转义特殊字符

如果你希望用于这些路由约定的特殊字符之一实际上是 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
文档和示例 CC 4.0