Skip to content
核心概念

核心概念

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 使用确定性排序算法来匹配路由,避免歧义:

  1. 静态段优先/posts/new 优先于 /posts/$postId
  2. 路径段数量:段数多的路由更具体,优先匹配
  3. 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-routerRegister 接口。所有 Hook(useParamsuseSearchuseNavigate 等)都从这个接口读取类型,从而实现全局类型推断。


导航状态与生命周期

每次导航经过以下生命周期:

路由匹配(确定目标路由树)
  ↓
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 插件自动生成的路由树文件