路由与数据获取:React Router / TanStack Router / SSR / RSC
0. 元信息
- 主题路径:
docs/topics/react-app/subtopics/routing-and-data-fetching/README.md - 父主题:
react-app - 主分类:前端与客户端
- 辅助分类:Web 与后端
- 适合对象:会写 React 应用、想理解 SSR / RSC 与 data router 的开发者
- 建议周期:1.5~2 周
- 前置知识:
components-and-jsx、hooks-and-state-management、HTTP - 最终目标:能在 Next.js / Remix / Vite + React Router 之间做选型并实现
1. 学习路线
客户端路由基础 → data router(loader/action/revalidate)→ Suspense 与错误边界
→ SSR / SSG / ISR → RSC 与 Server Actions → 选型:Next.js / Remix / TanStack Start
2. 阶段周数分配
精简子主题不固定周数。按 §3 顺序完成,重点是“路由不再是页面跳转,而是数据契约”。
3. 九阶段表
| 阶段 | 核心知识 | 实践产出 | 学会标准 |
|---|---|---|---|
| 1 | History API、hash vs browser router、嵌套路由 | 多页 demo | 能解释“为什么 hash 模式 SEO 差” |
| 2 | React Router v6/v7、<Outlet>、layout route | 登录 + 商品页 | 写嵌套布局不写错 Outlet |
| 3 | Loader / action / revalidator、<Form> | 搜索 + 提交 | 不在 useEffect 里拉数据 |
| 4 | TanStack Router:类型安全、search params 校验 | 同上,类型化 | 路由参数能 TS 推导 |
| 5 | ErrorBoundary、404、401、redirect | 错误页 | 知道 loader throw 会进 ErrorBoundary |
| 6 | Code split、lazy + Suspense | 大页面懒加载 | 解释 chunk 拆包策略 |
| 7 | SSR 基础:renderToString / hydration / mismatch | Next.js Pages Router 体验 | 能定位 hydration warning |
| 8 | App Router:Server Component、Client Component、streaming、partial prerendering | 完整电商雏形 | 能解释 'use client' 边界 |
| 9 | Server Actions、useActionState / useFormStatus / useOptimistic | 表单 + 乐观更新 | 能解释 Server Action 与 REST 区别 |
4. 第一周任务
精简版省略固定日程。优先完成“React Router v6 data router + 嵌套布局 + loader 拉数据 + ErrorBoundary”。
5. 阶段通用验收
精简版省略;要求每个路由都有对应数据契约与失败测试。
6. 最终验收
精简版省略;以 §3 第 9 阶段和 §9.4 案例口述检查为准。
7. 综合项目
精简版省略;成果并入父主题电商 SPA 的“路由 + 数据层 + RSC 重构备选”。
本主题贡献
- 职责 1(data router 取代
useEffect拉数据):父主题“商品列表 / 详情 / 购物车”路由全部走 React Router v7loader+ TanStack Query 缓存,禁用“组件useEffect里 fetch”(呼应 §9.4 #1、§9.5 思维难点);loader中通过request.signal传递 AbortSignal,路由切换旧请求自动取消,杜绝 §9.4 #2 旧请求覆盖。 - 职责 2(嵌套路由 + ErrorBoundary + Suspense):在路由树为
/admin、/checkout、/products/:id各加一条<ErrorBoundary>节点与<Suspense fallback={<Skeleton/>}>;loader 抛错必进 ErrorBoundary,401 守卫统一返回redirect('/login?next=…')且不形成循环(§9.4 #5、#8)。 - 职责 3(SSR / RSC 边界与 hydration 排障):父主题“商品详情”用 Next.js App Router 实现——默认 Server Component 拉数据,仅“加入购物车按钮 / 收藏按钮”子树标
'use client';并在 README 写明 hydration mismatch 排障清单(移除Date.now()/window/localStorage,必要时<ClientOnly>),对应 §9.4 #3 / #4。 - 交付物 1:
apps/web跑通“React Router v7 data router + 嵌套布局 + loader 拉数据 + ErrorBoundary + 路由级lazy()拆包 +<Link prefetch="intent">”,并附vite build产物分析截图。 - 交付物 2:
apps/web-rsc(Next.js 15 App Router 雏形)跑通“Server Component 列表 + Client Component 按钮 + Server Action 下单 +useActionState+useOptimistic”,并演示revalidatePath('/cart')失效缓存。 - 交付物 3:TanStack Router 版本路由文件
src/routes/products.index.tsx演示validateSearch: z.object({ page: z.number().int().min(1), category: z.enum([...]) }),搜索参数完全类型化(§9.4 #9)。 - 交付物 4:在 CI 跑
pnpm dlx react-router-typegen/ Next.js 自带类型生成,保证路由参数在 IDE 自动补全;并跑 ZAP Baseline 扫一轮/products、/checkout作为前置基线。 - 指标 1:商品列表路由级 chunk ≤80 KB(gzipped),首屏 LCP ≤2.0s(本地 Lighthouse 移动端模拟),对应 §3 阶段 6 “能解释 chunk 拆包策略”。
- 指标 2:路由切换到
/products/:id时,DevTools Network 中旧 loader 请求 100% 被AbortController取消(无“幽灵请求”)。 - 指标 3:hydration mismatch warning 在 CI 控制台为 0 条;如出现则构建失败(
next build已强制零警告退出码)。
8. 推荐资料
精简版省略;使用 §9.3 和 §9.6 Source。
9. 学习资料汇聚(v0.3 自包含)
9.1 背景与动机
早期 React 是“组件跑在客户端、路由靠库”。数据获取放在 useEffect,导致瀑布、SEO 差、首屏慢。React Router v6.4 引入 data router,把数据获取推到路由层。Next.js App Router 把这思路推到 RSC——组件本身能选“跑在服务端还是客户端”,数据在服务端拉、HTML 流式返回。Remix 把 Web 标准(loader / action / Form)做对。
今天的选择不再是“用不用框架”,而是“用哪条 SSR / RSC 路径”。理解协议(HTTP 缓存、Suspense、streaming)比记 API 更重要。
9.2 概念地图
flowchart LR
SPA[客户端 SPA] --> RR[React Router / TanStack Router]
RR --> Data[loader / action / revalidate]
Data --> Suspense
Suspense --> SSG[SSG]
Suspense --> SSR[SSR]
Suspense --> ISR[ISR]
Suspense --> PPR[Partial Prerendering]
SSR --> RSC[Server Components]
RSC --> Action[Server Actions]
Action --> Optimistic[useOptimistic]
RSC --> Hydration
RSC --> Streaming
9.3 基础知识讲解
9.3.1 论文 / RFC
- React Server Components RFC ——核心动机。
- React 19 Actions RFC ——表单 mutation。
- RFC 9110 / 9111 (HTTP caching) ——理解 stale-while-revalidate。
- TanStack Router:完全类型化路由。
9.3.2 书 / 文档
9.3.3 博客
- Overreacted: The Two Reacts(Dan Abramov 讲 RSC 心智)。
- Vercel 博客:RSC 实战。
- TkDodo:React Query 与 RSC。
- Sam Selikoff:Remix vs Next.js。
- Lee Robinson:Next.js 教程。
9.3.4 人物
- Ryan Florence:React Router 联合作者。
- Michael Jackson:React Router / Remix 联合作者。
- Guillermo Rauch:Vercel 创始人,RSC 推手。
- Rich Harris:Remix 共同作者、Svelte 作者。
- Tanner Linsley:TanStack 维护者。
- Dan Abramov:Bluesky 上讲 RSC 心智。
9.3.5 方法
- Data router first:把数据放 loader,不放 effect。
- Code split by route:路由级懒加载。
- Streaming + Suspense:长任务分段返回。
'use client'on demand:默认 Server Component,需要交互或浏览器 API 才加。- Edge for cacheable,Node for stateful:渲染目标按数据特性选。
- Type-safe router:TanStack Router 自动生成类型;React Router 用类型工具。
9.4 经典问题与经典案例
| # | 问题 | 为什么重要 | 最简答案或证据 |
|---|---|---|---|
| 1 | loader 报错不显示 | 没 ErrorBoundary | 在路由树加 ErrorBoundary 节点 |
| 2 | loader race | 切换路由旧请求覆盖新 | RQ 自动取消;loader 用 request.signal |
| 3 | hydration mismatch | 服务端 / 客户端输出不同 | 移除 Date.now() / window / localStorage;或用 <ClientOnly> |
| 4 | 'use client' 放错位置 | 整树变成 client | 最小化 client 边界;只把交互子树标 client |
| 5 | Server Action 没刷新数据 | 没 revalidatePath | 在 action 调 revalidatePath('/cart') |
| 6 | route 文件名错 | 框架找不到 | Next.js App Router:page.tsx / layout.tsx / loading.tsx / error.tsx |
| 7 | 嵌套路由 Outlet 丢失 | 路径不匹配 | 父用 <Outlet/>;子用相对 path |
| 8 | 401 跳转循环 | 路由守卫写错 | 守卫返回 redirect('/login?next=...') |
| 9 | search params 类型丢 | useSearchParams 拿到 string | TanStack Router:validateSearch: z.object(...) |
| 10 | 路由懒加载闪屏 | chunk 没预取 | <Link prefetch="intent"> 或显式 router.preloadRoute |
9.5 学习难点
- 概念难点:RSC 不是“服务端组件 + SSR”。SSR 把组件在服务端跑一次发 HTML,RSC 让组件持续跑在服务端、不下载 JS。两者不互斥。
- 思维难点:路由既是 UI 边界,也是数据边界。loader / action 把 CRUD 写成 Web 标准。
- 工程难点:hydration 排障、client/server 边界、缓存层(HTTP、CDN、ISR、full route cache)。
9.6 技术标准与接口
9.6.1 Entity
| 名称 | 版本 | 组织 | 状态 |
|---|---|---|---|
| React Router | v7 | Remix Software | 活跃 |
| TanStack Router | 1+ | Tanner Linsley | 活跃 |
| Next.js | 15+ | Vercel | 活跃 |
| Remix | 2+ | Remix Software(已并入 RR v7) | 活跃 / 合并 |
| Astro | 4+ | Astro 团队 | 活跃 |
| W3C Fetch / URL | 标准 | WHATWG | 标准 |
9.6.2 Scope
- 路由库不替代后端;它们只决定“请求 vs 渲染”的边界。
- RSC 是 React 19 稳定特性;上游框架(Next.js)提供框架级实现。
- Server Actions 不是新协议,是“
POST到自己的 server function”,依赖运行时支持。
9.6.3 Structure
- 必会 API:
createBrowserRouter/RouterProvider/<Outlet>/loader/action/useLoaderData/useNavigation/useFetcher。 - 必会 App Router 约定:
layout.tsx/page.tsx/loading.tsx/error.tsx/route.ts/middleware.ts。 - 必会数据接口:
fetch+cache: 'force-cache' | 'no-store'/revalidate/tags。
9.6.4 Ecosystem
- 文件式路由:Next.js、Remix、Astro、SolidStart、TanStack Start。
- 类型安全:TanStack Router、tRPC、Eden Treaty。
- 预取:
<Link prefetch>、router.preload、RQ prefetch。 - 缓存层:HTTP cache、CDN、framework route cache、React cache()、RQ cache。
9.6.5 Depth Tiers
| 层级 | 可观察能力 |
|---|---|
| L0 | 知道路由 / SSR 存在 |
| L1 | 看得懂路由示例 |
| L2 | 能用 data router、能用 SSG / SSR |
| L3 | 能排 hydration mismatch、设计 client/server 边界 |
| L4 | 能设计 RSC 架构、写自定义 loader / revalidate |
本计划目标:L3。
9.6.6 Source
- reactrouter.com。
- tanstack.com/router。
- nextjs.org/docs。
- patterns.dev/。
- 引用快照:2026-07-30。
10. 常见误区
- 在
useEffect拉数据 - 没 ErrorBoundary
Date.now()在组件里'use client'写最大- 不写 loader 写 client fetch
- 不读 Next.js 缓存层
- route 切了不取消旧请求
- search params 一直用 string
- 不写 loading.tsx
- 把 Server Action 当万能胶水。
11. 所有知识点分类
- 编程语言 2. 数据结构与算法 3. 计算机基础 4. 工程技术 5. Web 与后端 6. 前端与客户端 7. 数据与人工智能 8. 项目与职业能力 9. 安全与可靠性
本计划归属:前端与客户端 主 + Web 与后端 辅。