Weadb Notes

React router 文件路由

刚刚·8 min read

在 React Router v8 中(继承了 Remix 的架构体系),官方推荐使用 Vite 插件结合文件系统路由。通过 @react-router/fs-routes,你可以实现扁平化路由协议(Flat Routes Convention)

下面将详细介绍如何在 React Router v8 中配置、定义和使用文件系统路由。


一、 核心配置

文件系统路由的核心原理是:通过 Vite 插件扫描 app/routes 目录下的文件,并根据特定的命名规则自动生成路由树。

1. 确保 Vite 配置正确 (vite.config.ts)

React Router v8 的文件系统路由依赖于其 Vite 插件 @react-router/dev/vite

// vite.config.ts
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [reactRouter(), tsconfigPaths()],
});

2. 在路由入口注册文件系统路由 (app/routes.ts)

在项目的 app/routes.ts 中,通过引入 @react-router/fs-routesflatRoutes 函数来启用它。

// app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default flatRoutes() satisfies RouteConfig;

注:flatRoutes() 会自动扫描 app/routes 文件夹并生成对应的路由表。


二、 文件命名规则与路由映射

扁平化路由规范(Flat Routes)主要通过**文件名中的点(.)和下划线(_)**来定义层级、动态参数和布局。

以下是具体的命名规则和映射关系:

1. 根路由 / 索引路由 (Index Routes)

使用 _index 表示当前路径下的默认页面。

  • 文件路径app/routes/_index.tsx
  • 对应 URL/ (首页)

2. 普通基础路由 (Basic Routes)

直接使用英文命名。

  • 文件路径app/routes/about.tsx
  • 对应 URL/about

3. 嵌套路由与多级路径 (Nested Paths)

使用 点号 (.) 代替传统的文件夹层级。这样可以保持 routes 目录扁平化,避免深层嵌套文件夹。

  • 文件路径app/routes/dashboard.settings.tsx
  • 对应 URL/dashboard/settings

4. 动态路由参数 (Dynamic Segments)

使用 美元符号 ($) 前缀表示动态参数。

  • 文件路径app/routes/posts.$id.tsx
  • 对应 URL/posts/:id (例如 /posts/123)
  • 获取参数:在组件或 Loader 中通过 params.id 获取。

5. 无路径布局路由 (Pathless Layout Routes)

如果你想让一组页面共享同一个布局(Layout),但不希望在 URL 中增加路径层级,可以在布局文件名最前面加上 下划线 (_)

  • 布局文件app/routes/_auth.tsx (编写 <Outlet /> 渲染子路由)
  • 子路由文件app/routes/_auth.login.tsx
  • 子路由文件app/routes/_auth.register.tsx
  • 对应 URL
    • /login (套用 _auth 的布局)
    • /register (套用 _auth 的布局)

6. 兜底路由 / 通配符路由 (Splat / Catch-all)

使用单美元符号 $ 捕获所有未匹配的路径。

  • 文件路径app/routes/$.tsx
  • 对应 URL/* (通常用于 404 页面)

三、 路由文件内的编写规范

在定义好文件结构后,每个路由文件(如 app/routes/posts.$id.tsx)的结构通常包含:数据加载器(Loader)、动作处理器(Action)和页面组件(Default Export)。

// app/routes/posts.$id.tsx
import { useLoaderData, useParams } from "react-router";
import type { Route } from "./+types/posts.$id"; // 自动生成的类型定义

// 1. Loader:在服务端(或客户端)渲染前获取数据
export async function loader({ params }: Route.LoaderArgs) {
  const postId = params.id;
  const post = await fetch(`https://api.example.com/posts/${postId}`).then(res => res.json());
  return { post };
}

// 2. 页面组件
export default function PostDetail() {
  const { post } = useLoaderData<typeof loader>();
  const params = useParams();

  return (
    <article>
      <h1>{post.title}</h1>
      <p>当前文章 ID: {params.id}</p>
      <div>{post.content}</div>
    </article>
  );
}

四、 基于文件夹的协同定位 (Co-location)

如果你觉得把所有路由文件都平铺在 app/routes 下会导致单个目录下文件过多,或者你想把样式表、子组件、测试文件和路由组件放在一起,React Router 支持“文件夹路由”。

只要将文件夹命名为对应的路由名称,并在内部创建 route.tsx 即可:

  • 传统文件写法app/routes/about.tsx
  • 等价文件夹写法
    app/
    └── routes/
        └── about/
            ├── route.tsx (必须命名为 route.tsx 作为入口)
            ├── about-header.tsx (局部子组件)
            └── styles.css (局部样式)

这两种写法生成的路由完全一致,方便你根据项目规模自由选择。


总结对照表

文件路径 (在 app/routes/ 下) 对应 URL 路径 是否包含父布局
_index.tsx / 根布局
about.tsx /about 根布局
dashboard.tsx /dashboard (充当布局) 根布局
dashboard.stats.tsx /dashboard/stats 嵌套在 dashboard.tsx
_auth.tsx (无,仅作布局) 根布局
_auth.login.tsx /login 嵌套在 _auth.tsx
users.$id.tsx /users/:id 根布局
$.tsx /any-wrong-path (404) 根布局