首页 / TanStack 生态入门教程 / Router 入门:路由概念与安装

TanStack 生态入门教程

Router 入门:路由概念与安装

本教程共 38 篇 · 第 15 篇 · 更新于 2026-07-27 · 约 12 分钟阅读

TanStackTanStack 生态入门教程TanStack Router路由路由树类型安全React Router安装

15. Router 入门:路由概念与安装

本节目标:搞清楚 TanStack Router 是什么、为什么用它,理解路由树和路由匹配的规则,装好包并跑通一个最小路由应用。学完你能用代码式路由搭出可导航的页面。

15.1 TanStack Router 是个啥

前面 11 章都在讲 TanStack Query,管的是数据。这一章开始换主题,讲路由(Routing)

路由说白了就是:URL 变了,显示哪个页面。访问 /about 显示关于页,访问 /posts/123 显示第 123 篇文章,这套规则就是路由。React 生态里最有名的路由库是 React Router,但 TanStack Router 凭几个硬实力杀了出来:

  • 100% 类型推导:你写 <Link to="/posts/123" />,TypeScript 会自动检查这个路由存不存在、参数对不对。写错一个字母直接红线。
  • 类型安全的搜索参数:URL 里 ?page=2&sort=desc 这串东西,在 TanStack Router 里能被校验、被类型化,改一个参数都有类型提示。
  • 内置 loader 缓存:路由切换前先拉数据,自带 SWR 风格的缓存,不用每次切换都重新请求。
  • 为客户端数据缓存而生:和 TanStack Query 天然集成,loader 里 prefetchQuery,组件里 useQuery,丝滑。
  • 文件式 + 代码式双支持:两种路由配置方式能混着用。

它不是 React Router 的替代品那么简单,而是把”类型安全”这件事推到了路由的每个角落。

15.2 路由树是什么

理解 TanStack Router 的关键,是理解路由树(Route Tree)

URL 和组件不是一一对应的简单映射,而是一棵树。访问 /blog/posts/123,渲染的不是单个组件,而是一棵嵌套的组件树:

<Blog>
  <Posts>
    <Post postId="123" />
  </Posts>
</Blog>

外层 Blog 是布局,里面 Posts 是列表容器,最里层 Post 才是具体文章。这种嵌套结构对应到文件,就是一棵路由树:

/routes
├── __root.tsx
├── index.tsx
├── about.tsx
├── posts/
│   ├── index.tsx
│   ├── $postId.tsx
├── settings/
│   ├── profile.tsx
│   ├── notifications.tsx

整棵树有个根节点 __root.tsx,它包住所有路由。每个文件对应一个路由,文件路径决定 URL 路径。TanStack Router 拿这棵树去匹配 URL,匹配上了就把对应的组件嵌套着渲染出来。

Note

路由树可以用文件式(按文件命名约定生成)或代码式(手动 createRoute 构建)两种方式。两种方式功能完全一样,文件式代码更少,是官方推荐。代码式更灵活,适合老项目接入。后面第 16、17 章分别讲。

15.3 路由的几种类型

路由树里每个节点是一种路由类型,搞清楚分类才好理解后面的内容。

15.3.1 根路由(Root Route)

整棵树的根,用 createRootRoute() 创建。它没有 path,但永远会被匹配,它的组件永远会渲染。可以理解为整个 App 的外壳,所有页面都套在它里面。

import { createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: () => <div>根布局,所有页面都包在这里</div>,
})

如果要在根路由里放上下文(比如 QueryClient),用 createRootRouteWithContext<T>(),第 21 章会用到。

15.3.2 基础路由(Basic Routes)

精确匹配某个路径的路由。/about/settings/notifications 都是基础路由,路径写死,URL 完全一致才匹配。

15.3.3 索引路由(Index Routes)

父路由被精确匹配、且没有子路由匹配时,显示的路由。文件名以 / 结尾表示索引,比如 posts.index.tsx 对应 /posts/

15.3.4 动态路由(Dynamic Routes)

路径段以 $ 开头的路由,能捕获 URL 的某一段。比如 posts.$postId.tsx 匹配 /posts/123params 里就有 { postId: '123' }$ 可以出现在路径任意一段,/posts/$postId/$revisionId 也行。

15.3.5 通配路由(Splat Routes)

路径只有 $ 的路由,匹配从 $ 开始到结尾的所有内容。files/$.tsx 能匹配 /files/documents/hello-world,捕获的字符串放在 params._splat 里。

Warning

v1 里通配路由的参数 key 是 _splat,同时兼容旧的 * key,v2 会移除 *。新项目直接用 _splat

15.3.6 可选路径参数

{-$paramName} 语法定义可选参数。posts.{-$category}.tsx 既能匹配 /posts(category 是 undefined),也能匹配 /posts/tech(category 是 "tech")。

15.3.7 布局路由(Layout Routes)

用来包裹子路由,提供共享布局、loader、错误边界等。文件名是 app.tsx,子路由用 app.dashboard.tsxapp.settings.tsx 这种命名,app.tsx 就成了它们的布局。

// app.tsx
import { Outlet, createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/app')({
  component: () => (
    <div>
      <h1>App 布局</h1>
      <Outlet /> {/* 子路由渲染在这里 */}
    </div>
  ),
})

访问 /app/dashboard 时,渲染的是 <AppLayout><Dashboard /></AppLayout>,外层布局套着子页面。

15.3.8 无路径布局路由(Pathless Layout Routes)

下划线 _ 开头的路由,有布局功能但不占 URL 路径段。_pathlessLayout.tsx 包住 _pathlessLayout.a.tsx_pathlessLayout.b.tsx,访问 /a 渲染 <PathlessLayout><A /></PathlessLayout>,URL 里没有 _pathlessLayout 这一段。

适合”一组页面共享布局但不想在 URL 体现”的场景,比如登录前后的布局切换。

15.3.9 非嵌套路由

文件名里用 _ 后缀(注意是后缀,不是前缀)能让路由脱离父级嵌套。posts_.$postId.edit.tsx 虽然名字带 posts,但渲染时<Posts> 组件,直接渲染 <PostEditor>

15.4 路由匹配规则

路由这么多,URL 来了先匹配谁?TanStack Router 会把所有路由按特异性排序,最具体的优先匹配:

  1. 索引路由(最优先)
  2. 静态路由(从长到短)
  3. 动态路由(从长到短)
  4. 通配路由(最后)

不管你定义路由的顺序怎么样,最后都按这个规则排序。所以 /posts/featured 会比 /posts/$postId 先匹配,特定路径不会被动态路径抢走。

举个例子,路由树是这样的:

Root
  - /
  - about
  - about/us
  - blog
    - /
    - new
    - $postId
  - *

访问 /blog/my-post 的匹配过程:

Root
  ❌ /
  ❌ about
  ❌ about/us
  ⏩ blog
    ❌ /
    ❌ new
    ✅ $postId   // 匹配上

访问 /not-a-route 这种没定义的路径,会落到 * 通配路由上,用来做 404 页面。

15.5 安装

15.5.1 用脚手架快速创建

新项目最快的方式是用官方 CLI:

npx @tanstack/cli create --router-only

CLI 会问你几个问题:用文件式还是代码式、要不要 TypeScript、要不要 Tailwind、要不要初始化 Git。一路选完,一个能跑的项目就生成好了。

Tip

--router-only 表示只装 Router,不装 Start(Start 是基于 Router 的全栈框架,第 22 章讲)。如果你想直接上全栈,去掉这个参数。

15.5.2 手动装到现有项目

老项目接入,手动装包就行。

前置要求

  • React v18 或更高(要支持 createRoot
  • react-dom v18 或更高
  • 推荐 TypeScript v5.3+(不是必须,但 TanStack Router 的类型安全是核心卖点,不用 TS 等于白瞎)

安装

npm install @tanstack/react-router
# 或
pnpm add @tanstack/react-router
# 或
yarn add @tanstack/react-router

装完检查 package.json,能看到 @tanstack/react-router 的版本号就行:

{
  "dependencies": {
    "@tanstack/react-router": "^1.170.18"
  }
}
Note

TanStack Router 当前只支持 React(配 ReactDOM)和 Solid。React Native、Vue、Angular 暂不支持。本教程只讲 React 适配器。

15.6 搭一个最小代码式路由应用

装完包,咱们手搓一个能跑的最小路由,用代码式路由(文件式下一章讲)。这样你能直观看到路由树长啥样。

15.6.1 创建根路由

新建 src/router.tsx,先建根路由:

import {
  createRootRoute,
  createRoute,
  createRouter,
  Outlet,
} from '@tanstack/react-router'

// 根路由,所有路由的祖宗
const rootRoute = createRootRoute({
  component: () => (
    <div>
      <nav>
        <a href="/">首页</a> | <a href="/about">关于</a>
      </nav>
      <Outlet />
    </div>
  ),
})

createRootRoute 创建根路由,component 里放个简单的导航和 <Outlet />Outlet 是子路由渲染的占位符,相当于”子页面塞这里”。

15.6.2 创建子路由

加两个子路由:首页和关于页。

const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/',
  component: () => <h1>这是首页</h1>,
})

const aboutRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/about',
  component: () => <h1>这是关于页</h1>,
})

每个子路由都要指定 getParentRoute 告诉它爹是谁,path 是匹配的 URL,component 是渲染的内容。

15.6.3 组装路由树

把根路由和子路由拼成一棵树:

const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])

addChildren 接收一个数组,把子路由挂到父路由下。多层嵌套就一层层 addChildren 套下去。

15.6.4 创建 Router 实例

const router = createRouter({
  routeTree,
  defaultPreload: 'intent', // 鼠标悬停时预加载,体验更好
})

// 类型注册,让 TypeScript 认识你的路由
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

最后那个 declare module 很重要,它把你的 router 实例的类型注册给 TanStack Router,后面用 <Link to="..."> 时才能享受类型检查。漏了这步,类型安全就废了一半。

15.6.5 挂载到应用

import { RouterProvider } from '@tanstack/react-router'
import ReactDOM from 'react-dom/client'

const root = ReactDOM.createRoot(document.getElementById('root')!)

root.render(<RouterProvider router={router} />)

RouterProvider 把 router 实例注入到 React 树,整个应用就能用了。

15.6.6 跑起来

启动开发服务器(npm run dev),访问 / 看到首页,点导航跳到 /about 看到关于页。最小应用就成了。

Warning

注意上面导航用的是原生 <a href>,会触发整页刷新。生产里要用 <Link> 组件(第 18 章讲),这里为了演示最简原理先用 <a>

15.7 和 React Router 对比

很多人会问:我已经在用 React Router,有必要换吗?看张对比表(节选自官方):

能力TanStack RouterReact Router
类型安全路由完整支持部分(1/5)
类型安全路径参数支持支持
类型安全搜索参数支持不支持
搜索参数 Schema 校验支持不支持
路由上下文类型安全支持不支持
SWR 风格 loader 缓存支持不支持
文件式路由支持支持
代码式路由支持支持
虚拟/编程式文件路由支持支持
路由预取支持支持
预取延迟控制支持需自定义
导航阻塞(useBlocker)支持(含硬刷新/跨源)部分(不含硬刷新/跨源)
Devtools内置社区插件
搜索参数中间件支持不支持

简单总结:React Router 够用、稳定、生态广;TanStack Router 在类型安全搜索参数管理上甩开对手一截,loader 缓存和与 Query 集成也是亮点。

Tip

选型建议:新项目、用 TypeScript、重视类型安全和搜索参数,选 TanStack Router。老项目已经在 React Router 上跑得好好的,没强需求不用折腾。如果是 TanStack Start(第 22 章)项目,Router 是内置的,没得选也不用选。

15.8 常见坑

坑一:忘了 declare module 注册类型。 不注册的话,<Link to="/abc"> 不会检查路由是否存在,等于丢了类型安全。每次创建 router 后都要加这段。

坑二:用 React Router 的思维套 TanStack Router。 React Router 是 <Route path="/" element={<Home />} /> 这种声明式,TanStack Router 是先建路由树再 createRouter。思路不一样,别硬套。

坑三:根路由忘了 <Outlet /> 根路由组件里不写 <Outlet />,子路由的内容渲染不出来,页面一片空白。我第一次写就栽在这。

坑四:动态路由参数取错。 posts.$postId.tsx 的参数 key 是 postId,不是 posts.$postId。取参数用 Route.useParams() 返回 { postId: '123' }

坑五:CLI 装的包名写错。 网上老文章可能写 @tanstack/router,正确包名是 @tanstack/react-router(React 适配器)。CLI 不会装错,手动装时别抄错。

15.9 小结

这一章你认识了 TanStack Router:

  • 一款 100% 类型安全的路由库,类型安全和搜索参数管理是核心卖点。
  • 路由树组织 URL 和组件的嵌套关系,根路由 + 子路由层层挂载。
  • 路由分基础、索引、动态、通配、布局、无路径布局等多种类型。
  • 路由匹配按特异性排序,索引 > 静态 > 动态 > 通配。
  • 装包用 @tanstack/react-router,新项目用 CLI,老项目手动装。
  • 代码式路由用 createRootRoute + createRoute + createRouter 三步搭起来。

下一章讲文件式路由,让你不用手写路由树,靠文件命名约定自动生成。那是官方推荐的玩法,代码更省。