React Server Components 实战
React Server Components 实战:架构革命与渐进迁移
引言
React Server Components(RSC)是 React 自 Hooks 之后最深远的一次架构变革。它允许组件在服务端执行、直接访问数据库与文件系统、将结果以序列化流的形式下发给客户端——而客户端只收到「已经渲染完成的部分」与必要的交互脚本。本文将从 RSC 与 SSR 的区别讲起,剖析组件类型体系、渲染流协议、数据边界与渐进式迁移路径,帮助读者判断自己的项目是否适合引入 RSC。
一、RSC 与 SSR 的本质区别
很多人把 RSC 误认为「SSR 的另一个名字」,这是最常见的误解。两者虽然都涉及服务端渲染,但解决的问题截然不同。
概念对比
flowchart TB
classDef a1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef a2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef a3 fill:#e0f7fa,stroke:#00838f,color:#004d40
classDef a4 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
subgraph SSR[传统 SSR]
direction LR
S1[服务端渲染 HTML] --> S2[客户端下载全部 JS]
S2 --> S3[客户端水合整个应用]
S3 --> S4[服务端代码最终仍需下载]
end
subgraph RSC[React Server Components]
direction LR
R1[服务端组件执行 不产生 JS] --> R2[序列化渲染流下发]
R2 --> R3[客户端只下载交互部分]
R3 --> R4[服务端代码永不进 bundle]
end
click R2 "https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023" "RSC 官方说明"
class SSR a2
class S1 a2
class S2 a2
class S3 a2
class S4 a2
class RSC a1
class R1 a3
class R2 a3
class R3 a4
class R4 a4
一句话区别:SSR 让「HTML 先到」,RSC 让「组件根本不下发」——服务端组件的代码永远不进入客户端的 JavaScript Bundle。
二、组件类型体系:服务端与客户端的边界
RSC 引入了一个关键概念:组件边界(Component Boundary)。一个应用由服务端组件与客户端组件共同构成,通过文件约定或显式标记区分。
组件类型全景
flowchart TB
classDef b1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef b2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef b3 fill:#e0f7fa,stroke:#00838f,color:#004d40
classDef b4 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[组件体系] --> B[服务端组件 RSC<br/>默认 无交互]
A --> C[客户端组件<br/>'use client' 标记]
A --> D[共享组件<br/>双端皆可]
B --> B1[可访问数据库/文件系统]
B --> B2[可直接调用 secret API]
B --> B3[不产生 JS 不占 bundle]
C --> C1[可使用 useState/Effect]
C --> C2[可监听事件交互]
C --> C3[代码会进客户端 bundle]
D --> D1[只使用 props 渲染]
D --> D2[避免服务端专属 API]
click B "https://react.dev/reference/rsc/server-components" "RSC 参考文档"
click C "https://react.dev/reference/rsc/use-client" "'use client' 文档"
class A b1
class B b2
class C b3
class D b4
class B1 b2
class B2 b2
class B3 b2
class C1 b3
class C2 b3
class C3 b3
class D1 b4
class D2 b4
边界规则速查
| 能力 | 服务端组件 | 客户端组件 | 共享组件 |
|---|---|---|---|
| 访问数据库 | ✅ | ❌ | ❌ |
| 使用 Hooks | ❌(有限) | ✅ | 看上下文 |
| 事件监听 | ❌ | ✅ | ✅ |
| 渲染子组件 | ✅ 任意 | 仅客户端 | ✅ |
| 进客户端 bundle | ❌ | ✅ | 条件性 |
关键规则:客户端组件不能导入服务端组件(因为服务端组件代码不在客户端);反之服务端组件可以导入客户端组件——数据流永远是「服务端 → 客户端」。
三、RSC 渲染流:序列化协议详解
RSC 不是「服务端输出 HTML」这么简单,它输出一种特殊的序列化流(Flight 协议),包含组件树结构、props 引用与延迟渲染的 Promise。
Flight 协议的工作方式
flowchart LR
classDef f1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef f2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef f3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[服务端执行组件树] --> B[序列化 RSC 流]
B --> C[流式传输至客户端]
C --> D[客户端解析重建]
D --> E[渲染为 DOM]
B --> F[含引用表 module refs]
B --> G[含 Promise 待处理插槽]
G --> H[数据就绪后补发]
click B "https://react.dev/reference/rsc/functions" "RSC 函数参考"
class A f1
class B f1
class C f2
class D f3
class E f3
class F f2
class G f2
class H f3
序列化边界的类型
服务端组件的 props 会被序列化传输,但并非所有值都能序列化:
- ✅ 可序列化:字符串、数字、布尔、数组、普通对象、React 元素
- ❌ 不可序列化:函数(除引用)、Date、Map、类实例
- 🔶 特殊处理:Promise(延迟插槽)、客户端组件引用(module ref)
flowchart TD
classDef c1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef c2 fill:#ffebee,stroke:#c62828,color:#b71c1c
classDef c3 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[props 序列化] --> B{值类型判断}
B -->|原始类型/数组/普通对象| C[直接编码]
B -->|Promise| D[延迟插槽 就绪补发]
B -->|客户端组件引用| E[module ref 指向客户端 chunk]
B -->|函数| F[渲染报错 不可传输]
B -->|Date/Map/类实例| G[需要序列化适配]
click D "https://react.dev/reference/rsc/use" "use() 处理 Promise"
class A c1
class B c1
class C c3
class D c3
class E c3
class F c2
class G c2
四、数据获取:服务端直达数据库
RSC 最大的工程价值在于消灭了数据请求层:服务端组件可以直接 await 数据库查询,无需设计 REST/GraphQL 接口、无需加载态、无需客户端缓存同步。
传统客户端请求 vs RSC 直接读取
sequenceDiagram
participant U as 用户
participant R as 服务端组件
participant D as 数据库
participant C as 客户端组件
U->>R: 请求页面
R->>D: 直接 SQL 查询(无需 API 层)
D-->>R: 返回数据
R->>C: 渲染结果序列化下发
Note over C: 客户端零请求零加载态
数据获取模式对比
flowchart TB
classDef d1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef d2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef d3 fill:#e0f7fa,stroke:#00838f,color:#004d40
subgraph 传统模式
direction LR
T1[客户端组件] --> T2[useEffect 请求]
T2 --> T3[加载态 错误态]
T3 --> T4[缓存失效 重复请求]
end
subgraph RSC 模式
direction LR
R1[服务端组件] --> R2[直接 await 查询]
R2 --> R3[渲染即数据]
R3 --> R4[无加载态 无重复]
end
subgraph 混合模式
direction LR
H1[静态内容 RSC] --> H2[交互区域客户端]
H2 --> H3[需要时再请求]
H3 --> H4[React Query 缓存]
end
click R2 "https://react.dev/learn/server-components" "服务端组件学习文档"
class 传统模式 d2
class T1 d2
class T2 d2
class T3 d2
class T4 d2
class RSC模式 d1
class R1 d3
class R2 d3
class R3 d3
class R4 d3
class 混合模式 d3
class H1 d3
class H2 d3
class H3 d3
class H4 d3
五、Suspense 与流式补发
RSC 与 Suspense 深度集成:服务端组件中可以 await 任意 Promise,挂起期间流式输出占位内容,数据就绪后补发真实内容——这与 SSR 流式渲染一脉相承,但补发的单元是组件树而非 HTML 片段。
延迟插槽的生命周期
sequenceDiagram
participant S as 服务端
participant B as 浏览器
participant P as Promise
S->>S: 渲染组件树
S->>P: 遇到 await 挂起
S-->>B: 输出 Suspense fallback
P-->>S: 数据就绪
S-->>B: 流式补发组件子树
B->>B: 替换占位 无需水合
使用要点
javascript123456789101112131415async function Article({ id }) { // 服务端组件中直接 await const post = await db.query("SELECT * FROM posts WHERE id = $1", [id]); return ( <article> <h1>{post.title}</h1> <section>{post.content}</section> </article> ); } // 挂起时由 Suspense 兜底 <Suspense fallback={<ArticleSkeleton />}> <Article id={id} /> </Suspense>
六、渐进式迁移路径
RSC 不是「全有或全无」的赌注——它可以按页面、按区域渐进式落地。以 Next.js App Router 与 TanStack Start 为代表的框架已内置 RSC 支持,迁移路径通常如下:
四步迁移法
flowchart TB
classDef m1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef m2 fill:#fff3e0,stroke:#f57c00,color:#e65100
classDef m3 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[第一步 框架升级] --> B[切换到支持 RSC 的框架<br/>或升级到 Next 13+/TanStack Start]
B --> C[第二步 静态页面先行]
C --> D[把无交互的展示组件<br/>标记为服务端组件]
D --> E[第三步 数据下沉]
E --> F[数据库查询移到服务端组件<br/>移除客户端请求层]
F --> G[第四步 交互区隔离]
G --> H[交互组件保持客户端<br/>边界最小化]
click B "https://nextjs.org/docs/app" "Next.js App Router"
click D "https://react.dev/reference/rsc/server-components" "服务端组件参考"
class A m1
class B m1
class C m2
class D m2
class E m2
class F m3
class G m2
class H m3
迁移注意事项
| 注意点 | 说明 |
|---|---|
| 客户端组件边界 | 尽量下沉到交互叶子节点 |
| props 序列化 | 服务端传函数给客户端会报错 |
| 副作用位置 | 服务端组件不可用 useEffect |
| 测试策略 | 服务端组件需在 Node 环境测试 |
| 缓存策略 | 静态 RSC 可缓存,动态部分区分 |
七、RSC 的适用场景判断
RSC 是强大的工具,但不是每个项目都适合。决策前先问三个问题:
flowchart TD
classDef p1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef p2 fill:#fff3e0,stroke:#f57c00,color:#e65100
classDef p3 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[是否适合 RSC] --> B{页面是否大量服务端数据}
B -->|是 博客/电商/内容站| C{交互复杂度}
C -->|低| D[高度适合 强烈推荐]
C -->|中| E[适合 交互区隔离]
B -->|否 纯 SPA 仪表盘| F{交互为主}
F -->|是| G[收益有限 可等成熟]
F -->|否| D
E --> H[渐进迁移 分页落地]
click D "https://react.dev/learn/start-a-new-react-project" "新项目选择"
class A p1
class B p1
class C p2
class D p3
class E p3
class F p2
class G p3
class H p3
八、总结
React Server Components 是对「客户端渲染世界」的一次结构性校正:
- 代码不下发:服务端组件零 JS 成本,bundle 显著瘦身;
- 数据直连:数据库查询直达渲染层,消灭中间 API 层;
- 组件边界:'use client' 标记交互区域,边界即架构;
- 流式补发:Suspense + Flight 协议实现按需交付;
- 渐进迁移:框架内置支持,可逐页落地降低风险。
实践建议:内容型站点、管理后台中的展示区、数据密集列表是 RSC 的最佳切入点;交互密集型应用可等待生态进一步成熟。理解组件边界与序列化约束,是使用 RSC 的第一课。下一篇我们将探讨 React 表单体系的完整设计模式。
