核心概念
TanStack Router 的设计围绕几个核心概念展开:类型安全的路由树、文件路由 vs 代码路由、路由匹配与排序、以及搜索参数作为一等公民。
路由树(Route Tree)
所有路由(无论文件路由还是代码路由)最终都会组织成一棵路由树。每个路由节点包含:
| 属性 | 说明 |
|---|---|
path |
URL 路径段(根路由无 path) |
component |
匹配时渲染的组件 |
loader |
渲染前预加载数据的异步函数 |
beforeLoad |
loader 之前执行的守卫/中间件 |
validateSearch |
搜索参数的验证与解析 |
children |
子路由列表 |
__root__ ← 根路由(无路径,始终匹配)
├── / ← index 路由
├── /about
├── /posts
│ ├── / ← posts/index
│ └── /$postId ← 动态路由
└── _authenticated ← 路径无关布局(不改变 URL)
├── /dashboard
└── /settings🔬 深入原理:Vite 插件在构建时扫描
src/routes/目录,根据文件结构生成routeTree.gen.ts。这个文件包含了完整的路由树定义和所有类型信息,是类型推断的来源。
文件路由 vs 代码路由
| 维度 | 文件路由(推荐) | 代码路由 |
|---|---|---|
| 定义方式 | 文件名即路径,createFileRoute() |
手动 createRoute() 构建树 |
| 类型生成 | Vite 插件自动生成 | 手动定义 |
| 路由树 | 自动生成 routeTree.gen.ts |
手动组装 addChildren() |
| 适用场景 | 大多数应用 | 需要动态路由、微前端、特殊需求 |
| 学习曲线 | 低(约定优于配置) | 中(需理解路由 API) |
// 文件路由
export const Route = createFileRoute('/posts/$postId')({
component: PostDetail,
})
// 代码路由
const rootRoute = createRootRoute({ component: RootLayout })
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
component: PostsList,
})
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
component: PostDetail,
})💡 最佳实践:除非有特殊需求(如运行时动态注册路由),否则始终使用文件路由。它减少样板代码、消除手动维护路由树的负担、并提供最佳的 IDE 体验。
路由匹配与排序
TanStack Router 使用确定性排序算法来匹配路由,避免歧义:
- 静态段优先:
/posts/new优先于/posts/$postId - 路径段数量:段数多的路由更具体,优先匹配
- Splat 路由(
$/ catch-all)优先级最低
/posts/new ← 静态匹配 "new"
/posts/$postId ← 动态匹配任何单段
/posts/$ ← 匹配所有子路径🚨 陷阱:确保
/posts/new和/posts/$postId不同时存在但语义冲突。如果用户访问/posts/new,它会被静态路由捕获,$postId路由永远不会收到"new"这个值。
路径无关布局(Pathless Layout)
以 _ 开头的路径段是路径无关布局——它们不会出现在 URL 中,但会在路由树中创建层级:
文件结构 URL 结构
src/routes/
├── __root.tsx
├── _authenticated.tsx ← 不存在于 URL
│ ├── dashboard.tsx → /dashboard
│ └── settings.tsx → /settings
└── _public.tsx ← 不存在于 URL
├── index.tsx → /
└── login.tsx → /login💡 最佳实践:路径无关布局是组织认证守卫、共享布局、RBAC 角色检查的理想方式。它们提供逻辑分组而不污染 URL。
类型安全的三层保障
TanStack Router 的类型安全体现在三个层次:
1. 路由注册(全局类型注入)
// router.tsx — 一次性注册,全局生效
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}2. 路径参数类型推断
// $postId 自动推断为 string
const { postId } = Route.useParams() // postId: string ✅
3. 搜索参数验证
// validateSearch 定义后,useSearch 自动推断
const search = Route.useSearch()
// search: { page: number; sort: 'newest' | 'oldest' } ✅
🔬 深入原理:类型注册使用 TypeScript 的 declaration merging 将路由器的类型信息注入到
@tanstack/react-router的Register接口。所有 Hook(useParams、useSearch、useNavigate等)都从这个接口读取类型,从而实现全局类型推断。
导航状态与生命周期
每次导航经过以下生命周期:
路由匹配(确定目标路由树)
↓
validateSearch(解析 & 验证搜索参数)
↓
beforeLoad(守卫 / 重定向 / 预检查)← 父 → 子,串行
↓
loader(数据预加载)← 父子并行
↓
组件渲染🚨 陷阱:
beforeLoad是串行执行的(父路由先于子路由),而loader是并行的(父子同时发起)。这意味着不要在beforeLoad中做耗时操作——它会阻塞整个导航。
术语速查
| 术语 | 说明 |
|---|---|
| Route Tree | 所有路由组成的树状结构 |
| Root Route | 根路由 __root.tsx,所有路由的祖先 |
| Pathless Layout | _ 前缀路由,提供布局和守卫但不影响 URL |
| Outlet | 子路由的渲染出口,类似 React Router 的 <Outlet /> |
| Loader | 路由级别的数据预加载函数 |
| beforeLoad | loader 之前的守卫函数,可执行重定向 |
| Search Params | URL 查询参数,JSON 序列化,类型安全 |
| routeTree.gen.ts | Vite 插件自动生成的路由树文件 |