React Server Components
1. 版本与定位
| 阶段 | 说明 |
|---|---|
| 概念与实验 | React 18 起与 流式 SSR、Flight 协议在框架(如 Next.js App Router)中落地 |
| 稳定 | React 19 将 RSC、Server Actions、相关 bundler 约定标为稳定能力 |
RSC 不是「在服务器里多跑一遍 createRoot」那么简单:服务器用 React Server 渲染器产出 Flight payload(序列化组件树 + 数据引用),客户端用 React Client(Flight) 解析并挂载 Client Components,Server Component 代码默认不进浏览器 bundle。
与 06-Suspense 的关系:服务端可对未就绪片段先流式发送 fallback,再在数据就绪后追加内容,与客户端 Suspense 协议一致。
2. Server Components vs Client Components
2.1 对比表
| Server Component | Client Component | |
|---|---|---|
| 运行环境 | 仅服务器(构建/请求时) | 浏览器(+ 水合) |
| 指令 | 默认;无需标注 | 文件顶 "use client" |
| Hooks | ❌ useState、useEffect 等 | ✅ |
| 浏览器 API | ❌ window、document | ✅ |
| 事件 | ❌ onClick 等 | ✅ |
| 数据访问 | 可直接 await DB、读文件、调内部 API | 通常 fetch / 调 Server Action |
| 打包 | 不发送组件实现到客户端 | 发送 JS,需水合 |
2.2 边界怎么划(经验法则)
- 默认 Server:纯展示、读数据、拼布局,无交互。
- 必须 Client:状态、 effect、订阅、浏览器 API、第三方强依赖 DOM 的库。
- 组合:Server 父组件
importClient 子组件;Client 父组件不能importServer 子组件(只能children插槽由服务器注入已渲染结果)。
// Server(默认)
export default async function Page() {
const rows = await db.query('...');
return <Table rows={rows} />; // Table 若是 Client,仅其边界以下进 bundle
}
// Client
'use client';
export function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}2.3 "use server" 是什么
标记 Server Actions(可在服务器执行的异步函数),供表单 action、客户端事件等调用。与「Server Component 文件」不同:Action 是函数引用经序列化传到客户端,不是整文件当组件渲染。
3. 端到端架构(无长源码)
3.1 双渲染器
| 包(概念) | 角色 |
|---|---|
| react-server(Fizz 等) | 在 Node/Edge 上渲染 RSC,写出 Flight 流 |
| react-client(Flight) | 在浏览器解析 Flight,创建 Client 组件实例并水合 |
| react-dom | 仍负责 DOM 提交;与 Flight 解析结果衔接 |
不必记具体文件名;读源码可从 ReactFizzServer.js(服务端任务)、ReactFlightClient.js(createFromReadableStream)入手。
3.2 一次请求的大致流程
sequenceDiagram
participant B as 浏览器
participant S as 服务器 RSC
participant F as Flight 流
participant C as Client 运行时
B->>S: 请求页面
S->>S: 渲染 Server 树 await 数据
S->>F: 序列化 chunk(含 Client 引用)
F-->>B: 流式响应
B->>C: 解析 Flight
C->>C: 加载 Client 组件 JS
C->>B: 水合交互区域- 服务器执行 Server Component 树,遇 Client 组件不执行其实现,只写入 Client Reference(id + 导出名)。
- 序列化 props、children、懒加载边界等到 Flight 格式(非「把 React 元素 JSON.stringify」那么简单)。
- 客户端按引用拉取对应 JS chunk,在标记的 Client 边界内水合。
- 水合只发生在 Client 子树;Server 输出已是 HTML/流中的结构,减少「空壳 + useEffect 拉数据」。
3.3 和经典 SSR 的区别
经典 SSR(如 renderToString) | RSC | |
|---|---|---|
| 输出 | 主要是 HTML 字符串 | HTML + Flight 数据(组件树描述) |
| 组件代码 | 常在同一 Node bundle 执行 | Server / Client 分包 |
| 数据 | 多在客户端或 getServerSideProps 重复 | 多在 Server Component 内直接读 |
| 交互 | 整页或大块水合 | 仅 Client 边界水合 |
4. Flight 与引用系统(总结)
4.1 传什么、不传什么
可序列化(常见):原始类型、普通对象/数组、部分内置类型(Date 等,视版本与配置)、Promise(由 Suspense 流式处理)、Server Action 引用、Client 组件引用。
不可直接传:任意函数、类实例、Symbol、DOM 节点、window 等。Client 需要的是 引用 id,由 bundler 在构建期登记。
4.2 Client Reference / Server Reference
| 类型 | 含义 |
|---|---|
| Client Reference | 服务器渲染时占位:「这里由客户端模块 X 的导出 Y 接管」;$$typeof + $$id 等内部标记 |
| Server Reference | Server Action 在客户端表单/事件里可调用,实际在服务器执行;经 registerServerReference 登记 |
服务器不能执行 Client Reference 的函数体;客户端不能在服务器环境直接跑 Server 函数实现,只能发请求触发。
4.3 流式与 Suspense
- 服务器渲染遇 await 或挂起:可先输出 Suspense fallback 的 HTML/Flight chunk,就绪后再发后续 chunk(流式 SSR)。
- 与客户端 06-Suspense 同一套「未就绪 → fallback → retry」思想,避免白屏等待最慢接口。
5. Server Actions(React 19 稳定)
5.1 是什么
在服务器运行的异步函数,从 Client 以 action={fn} 或事件处理器调用;常配合 useActionState、useFormStatus(见 04-Hooks §10)。
5.2 行为要点
| 点 | 说明 |
|---|---|
| 渐进增强 | <form action={serverAction}> 无 JS 也可提交(框架生成 action URL) |
| 序列化 | 仅传 引用 + 参数(如 FormData),不传函数源码 |
| 安全 | 必须在服务端校验权限与输入;Action 是公开端点 |
| 与 Transition | 可在 Transition / Action 流程中更新 UI、useOptimistic |
'use server';
export async function save(formData) {
await db.insert(formData.get('name'));
}
// Client
'use client';
export function Form() {
return (
<form action={save}>
<input name="name" />
<button type="submit">保存</button>
</form>
);
}6. 性能与使用场景
6.1 为什么能减小 bundle
- 重型依赖(Markdown、语法高亮、大工具库)若只在 Server Component 使用,不会打进客户端 JS。
- Client 边界应尽量下移到真正需要交互的叶子,避免整页
"use client"。
6.2 适合 / 不适合
| 适合 Server | 适合 Client |
|---|---|
| 读 DB、读 CMS、拼静态结构 | 表单即时校验、拖拽、动画 |
| 权限校验后读敏感数据 | 订阅 WebSocket、媒体 API |
| 大段只读 UI | 依赖 localStorage 的 UI |
6.3 数据获取模式
- 推荐:在 Server Component 里
await,结果作为 props 传给 Client 子组件。 - 避免:在 Server 里为了「用 Hook」强行包一层 Client;也避免 Client 里
useEffect再请求本可在服务器一次拿完的数据(除非需要浏览器隐私或实时性)。
7. 与 React 19 其它能力
| 能力 | 与 RSC 关系 |
|---|---|
use | 客户端读由 RSC 传下的 Promise / Context |
| 文档元数据 | <title> 等可在组件树中声明,由框架提升到 <head> |
| ref 作 prop | 减少 forwardRef,Client 边界组件更易组合 |
| Actions | 与 Server Actions、useActionState 统一异步提交模型 |
详见 01-核心架构概述。
8. 常见误区
- RSC ≠ 全站不能在客户端跑 React:只是默认在服务器渲染树;Client 岛仍需要 JS。
- 不是 React 19 才「发明」服务器渲染:19 是协议与生态稳定;Next 等框架在 18 已大量使用。
"use client"会向上影响导入它的模块:被 Client 引用的子模块也会进客户端包,注意依赖链。- Server Component 不能传事件处理器给 Client:事件必须在 Client 组件内定义或经 Server Action 间接触发。
- 安全:Server Component 里访问密钥安全;绝不能把密钥或信任边界放进传给 Client 的 props。
9. 源码与框架阅读路径
| 关注点 | 方向 |
|---|---|
| 服务端渲染任务 | packages/react-server — Fizz / createRequest、startWork |
| 客户端解析流 | packages/react-client — createFromReadableStream |
| 引用注册 | ReactServerReference.js、bundler 插件(框架侧) |
| 与 DOM 衔接 | react-dom 流式 API:renderToReadableStream 等 |
日常开发更多接触 框架约定(Next.js app/、Server Action 文件规则)而非直接调上述底层 API。
10. 总结
- RSC:服务器渲染不可下发的组件树 + Flight;客户端只水合 Client 边界。
"use client"/"use server":划分运行环境与 Action,不是装饰语法。- 流式 + Suspense:慢数据不阻塞整页首屏。
- Server Actions(19):类型安全的「远程过程调用 + 表单」,需服务端校验。
- 与并发客户端:Scheduler / Lanes 管浏览器主线程;RSC 管字节与首次 HTML——二者互补。
建议阅读顺序:02-Fiber → 06-Suspense → 本文 → 框架官方 RSC 文档。