TanStack Router 路由与数据加载
TanStack Router 路由与数据加载:类型安全的路由现代化
引言
在 React 生态中,路由方案长期被 React Router 垄断,但 TanStack Router 的出现正在改变这一格局。它把「类型安全」作为一等公民——路由路径、搜索参数、loader 数据、上下文全部由 TypeScript 推导,杜绝了字符串魔法。本文将从路由模型、文件约定、数据加载器、搜索参数四个维度剖析 TanStack Router,并通过与 React Router 的对比帮助读者判断何时值得迁移。
一、为什么需要新的路由方案
传统路由方案的核心痛点集中在三点:路径与组件的耦合靠字符串、loader 数据缺少类型、搜索参数需要手工解析校验。TanStack Router 用一套「路由树 + 代码生成 + 类型推导」的组合拳解决这些问题。
传统路由 vs TanStack Router
flowchart TB
classDef r1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef r2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef r3 fill:#e0f7fa,stroke:#00838f,color:#004d40
subgraph 传统方式
direction LR
T1[字符串路径配置] --> T2[运行时解析匹配]
T2 --> T3[手动类型断言]
T3 --> T4[错误容易潜伏]
end
subgraph TSR[TanStack Router]
direction LR
S1[文件/代码定义路由树] --> S2[生成 Route 类型]
S2 --> S3[编译期类型检查]
S3 --> S4[错误即时暴露]
S4 --> S5[IDE 智能提示]
end
click S2 "https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing" "文件路由文档"
class 传统方式 r2
class T1 r2
class T2 r2
class T3 r2
class T4 r2
class TSR r1
class S1 r3
class S2 r3
class S3 r3
class S4 r3
class S5 r3
核心特性一览
| 特性 | 说明 | 对比 React Router |
|---|---|---|
| 类型安全路由 | 路径/参数/数据全推导 | 需手工声明类型 |
| 文件路由约定 | routeTree.gen.ts 自动生成 |
手动配置路由表 |
| 内置 loader | 路由级数据预取 | 需借助 loaders(v7) |
| 搜索参数校验 | Zod/Valibot 集成 | 手动 parse |
| 路由级缓存 | staleTime/preload |
需自行实现 |
| 中间件 | 认证/日志/权限 | 需外层包裹 |
二、路由树模型与文件约定
TanStack Router 的核心模型是路由树(Route Tree)——所有路由节点组成一棵与 URL 层级对应的树,节点之间通过继承关系共享布局与上下文。
文件路由的目录结构
flowchart TB
classDef t1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef t2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef t3 fill:#e0f7fa,stroke:#00838f,color:#004d40
src[routes/] --> root[__root.tsx<br/>根布局与全局错误]
src --> layout[_app.tsx<br/>应用布局路由]
layout --> home[index.tsx<br/>首页]
layout --> posts[posts/]
posts --> list[posts/index.tsx<br/>文章列表]
posts --> detail[posts/$slug.tsx<br/>文章详情]
layout --> console[console.tsx<br/>控制台布局]
console --> cIndex[console/index.tsx]
console --> cLogin[console/login.tsx]
root --> layout
click detail "https://tanstack.com/router/latest/docs/framework/react/guide/path-params" "路径参数文档"
class root t1
class layout t1
class home t2
class posts t2
class list t2
class detail t3
class console t2
class cIndex t3
class cLogin t3
布局路由的嵌套继承
$slug.tsx 使用大括号占位符表示动态段(区别于 React Router 的 :slug 语法)。动态参数通过 useParams() 获取,且类型自动推导为 { slug: string }——改名、增删参数都会在编译期暴露所有引用点。
三、loader:路由级数据预取
loader 是 TanStack Router 数据流的枢纽:路由匹配时先执行 loader 获取数据,再渲染组件,天然支持 SSR 与预取。
loader 的执行时机
flowchart TB
classDef l1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef l2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef l3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[用户导航到 /posts/abc] --> B{路由是否匹配}
B -->|是| C{loader 是否已执行过}
C -->|缓存有效| D[直接渲染组件]
C -->|需刷新| E[执行 loader]
E --> F{loader 是否返回 Promise}
F -->|是| G[Suspense 等待]
F -->|否| H[同步获得数据]
G --> I[数据注入路由上下文]
H --> I
I --> J[渲染组件]
click E "https://tanstack.com/router/latest/docs/framework/react/guide/data-loading" "数据加载文档"
class A l1
class B l1
class C l1
class D l3
class E l2
class F l2
class G l3
class H l3
class I l2
class J l3
loader 与 TanStack Query 的协同
实践中 loader 通常与 TanStack Query 的 queryOptions 配合:loader 负责 prefetchQuery 预热缓存,组件内的 useQuery 直接读取缓存,避免双端重复请求,也保证了 SSR 与客户端数据一致。
sequenceDiagram
participant R as Router loader
participant Q as Query Cache
participant C as 组件
R->>Q: prefetchQuery(postDetailQuery(slug))
Q->>Q: 请求或读取缓存
Q-->>R: 数据预热完成
R-->>C: 路由匹配完成
C->>Q: useQuery(postDetailQuery(slug))
Q-->>C: 命中缓存 立即返回
Note over C,Q: 无闪烁、无重复请求
四、搜索参数:类型安全 + Zod 校验
搜索参数(URL query)是前端最容易「类型裸奔」的区域:useSearchParams 返回 URLSearchParams,取值全是字符串。TanStack Router 通过 validateSearch 与 Zod 集成,让搜索参数也成为类型推导的一部分。
搜索参数的生命周期
flowchart LR
classDef s1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef s2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef s3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[URL: /posts?page=2&tag=react] --> B[validateSearch 解析]
B --> C[Zod Schema 校验]
C --> D{校验结果}
D -->|成功| E[强类型 search 对象]
D -->|失败| F[触发错误边界/默认值]
E --> G[组件 useSearch 读取]
G --> H[搜索参数变化触发重载]
click B "https://tanstack.com/router/latest/docs/framework/react/guide/search-params" "搜索参数文档"
class A s1
class B s1
class C s2
class D s2
class E s3
class F s3
class G s2
class H s2
实践示例
typescript12345678const postListSearchSchema = z.object({ page: z.number().int().min(1).catch(1), tag: z.string().optional(), sort: z.enum(["latest", "hot"]).catch("latest"), }); // Route 内声明 validateSearch: postListSearchSchema.parse,
收益:非法参数被 Zod 归一化为默认值;useSearch 返回的类型对象带完整提示;搜索参数变化时,loader 依赖自动感知并重取数据——分页、筛选、排序这类「URL 即状态」的场景变得极其自然。
五、预取(Prefetching)与缓存策略
TanStack Router 内置路由级预取:鼠标悬停链接时即可预取目标路由的 loader 数据与组件 chunk,让导航「零等待」。
预取触发链
flowchart TD
classDef p1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef p2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef p3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[Link 组件渲染] --> B{用户交互}
B -->|悬停| C[触发 onMouseEnter 预取]
B -->|聚焦| D[触发 onFocus 预取]
B -->|Touch 开始| E[移动端预取]
C --> F[下载组件 chunk]
C --> G[执行目标 loader]
F --> H[导航时组件已就绪]
G --> H
H --> I[瞬时切换 无 loading]
click C "https://tanstack.com/router/latest/docs/framework/react/guide/preloading" "预取文档"
class A p1
class B p1
class C p2
class D p2
class E p2
class F p3
class G p3
class H p2
class I p3
缓存参数速查
| 参数 | 默认值 | 作用 |
|---|---|---|
gcTime |
30 分钟 | 缓存数据保留时长 |
staleTime |
0 | 数据过期时间,过期即重取 |
preload |
false |
是否开启预取 |
preloadDelay |
10ms | 悬停后延迟预取 |
structuralSharing |
true |
结构共享避免无谓重渲染 |
六、中间件:认证与权限的声明式表达
路由中间件让「登录拦截」「权限校验」「日志埋点」这类横切关注点声明式地挂在路由树上,而不是散落在每个组件的 useEffect 里。
中间件链路
flowchart TB
classDef m1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef m2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef m3 fill:#e0f7fa,stroke:#00838f,color:#004d40
classDef m4 fill:#ffebee,stroke:#c62828,color:#b71c1c
A[请求进入路由树] --> B[认证中间件]
B --> C{会话是否有效}
C -->|否| D[重定向登录页]
C -->|是| E[注入当前用户上下文]
E --> F[权限中间件]
F --> G{角色是否满足}
G -->|否| H[返回 403 页面]
G -->|是| I[注入权限标识]
I --> J[数据加载中间件]
J --> K[注入 loader 上下文]
K --> L[渲染最终组件]
click B "https://tanstack.com/router/latest/docs/framework/react/guide/middleware" "中间件文档"
class A m1
class B m1
class C m2
class D m4
class E m3
class F m1
class G m2
class H m4
class I m3
class J m1
class K m3
class L m3
中间件的类型渗透
中间件可以通过 beforeLoad/loader 返回上下文,类型会沿着路由树逐级传递——子路由的 loader 与组件中都能拿到父级注入的 user、permissions 等字段,且全程类型安全,无需任何 any。
七、TanStack Router vs React Router 选型
对比决策流程
flowchart TD
classDef f1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef f2 fill:#fff3e0,stroke:#f57c00,color:#e65100
classDef f3 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[路由选型] --> B{团队 TS 成熟度}
B -->|高| C{需要类型安全路由}
C -->|是| D[TanStack Router]
C -->|否| E{需要加载器与 SSR}
E -->|是| F[React Router v7]
B -->|中低| E
E -->|否| G{生态熟悉度}
G -->|React Router 熟练| F
G -->|愿意学习| D
D --> H[内置 loader/搜索校验/中间件]
F --> I[生态成熟 文档多]
click D "https://tanstack.com/router/latest" "TanStack Router 文档"
click F "https://reactrouter.com/" "React Router 文档"
class A f1
class B f1
class C f2
class D f3
class E f2
class F f3
class G f2
class H f3
class I f3
迁移注意事项
- 路径参数语法:
/posts/:slug→/posts/$slug; - 搜索参数:从「字符串散装」迁移到 Zod Schema;
- loader 位置:从组件内 effect 迁移到路由声明;
- 嵌套布局:理解路由树继承而非嵌套 Outlet 嵌套。
八、总结
TanStack Router 代表了 React 路由的现代化方向:
- 类型安全贯穿始终:路径、参数、数据、上下文四层全推导;
- loader 声明式数据流:路由匹配即数据就绪,天然支持 SSR;
- 搜索参数 Zod 校验:URL 状态强类型化,杜绝魔法字符串;
- 路由级预取:悬停即预载,导航零等待;
- 中间件体系:认证权限声明式挂载,类型沿树传递。
选择建议:新项目、TypeScript 重度团队、对类型安全有执念的团队,直接拥抱 TanStack Router;存量 React Router 项目若无切肤之痛,不必为迁移而迁移。下一篇我们将深入 TanStack Query 的缓存与请求状态机。
