第 30 章:RSC——Server Components 架构与通信协议
Server Components(RSC)是 React 19 最颠覆的一块:组件可以在服务端运行,只把渲染结果发给浏览器。本章不追脚手架怎么配(那是框架的事),只看它在源码里怎么实现的。
// 服务端组件(默认):能读数据库,没有 useState/useEffect
async function BlogList() {
const posts = await db.getPosts(); // 直接读数据,不用 API
return <div>{posts.map(p => <PostCard key={p.id} post={p} />)}</div>;
}
// PostCard 在 "use client" 文件里,是客户端组件
先记住这一句:RSC 是服务端把组件树跑一遍,序列化成一份 "flight" payload(JSON + 引用),发给客户端;客户端反序列化,拿到一棵 React 元素树,照常渲染。服务端组件不是"有生命的组件",它只是"服务端算一次,产出树"。
一图流:一次 RSC 请求的完整链路
服务端:跑组件,序列化成 flight
服务端入口是 createRequest(ReactFlightServer.js:777),它建立一次请求上下文,然后开始渲染组件树。renderModel(3198)负责把每个"模型"序列化:对象、数组、字符串、数字直接转 JSON;函数、类、React 元素这些 JSON 表达不了的东西,用特殊引用编码。最终的产物由 emitModelChunk(4222)一块块发出去。
关键点:服务端组件在服务端真的跑了一遍(renderElement,2102),所以它能读数据库、算业务逻辑。跑完只留下"结果"(序列化数据),没有可执行代码发给客户端。
flight 协议:JSON 加引用
序列化产物叫 flight payload,格式是"按 id 引用的模型块":
- 基本类型(字符串、数字、布尔、数组、普通对象)→ 原样 JSON。
- 客户端组件的引用 → 用一个特殊前缀(
$开头)编码成"模块引用":客户端知道这个"是个组件,但真正的实现在我这边某个模块里"。 - 服务端组件的结果 → 已经展开成一棵元素树了,直接序列化。
所以这份 payload 是"数据和引用"的混合:数据是服务端算好的,引用是留给客户端"去取模块实现"的钩子。
客户端:反序列化,还原元素树
客户端拿到 payload,parseModel(ReactFlightClient.js:5160)按 id 把模型块反序列化:看到 $ 前缀的引用,就去加载对应的客户端组件模块(resolveModel,2872),把它解析成真实的组件引用。最终拼出一棵完整的 React 元素树,塞给正常的渲染流程。第 6 章开始的 beginWork、diff、commit,对 RSC 渲染结果一视同仁。
边界:use client 和 use server
use client:这个文件的组件不在服务端跑,payload 里只留"模块引用",客户端自己去 import 真实模块。所以客户端组件有 state、effect,是"活的"。use server:定义服务端动作(Server Action),客户端能调它(第 31 章)。- 没有指令的服务端组件:只算一次,没有 state/effect。
这份"序列化数据 + 模块引用"的架构,让 React 能跨网络传输"组件树",而不用传代码。
和 SSR 的区别
容易混,一句话分清楚:
- SSR(Fizz):服务端把组件渲染成 HTML 字符串,发给浏览器直接显示;交互还是客户端重新跑一遍(水合)。
- RSC(Flight):服务端把组件渲染成 序列化数据,客户端反序列化出元素树再渲染。它不是"发 HTML 给用户看",是"发数据给 React 拼树"。
两者可以叠加:RSC 算数据 → SSR 发 HTML → 客户端水合,这正是 Next.js App Router 干的事。
动手实验
- 看 flight payload:在
emitModelChunk(ReactFlightServer.js:4222)打断点,看序列化出的模型块长什么样,$引用的格式。 - 看客户端还原:在
parseModel(ReactFlightClient.js:5160)打断点,观察引用怎么被解析成组件。 - 验证服务端组件无状态:服务端组件里
useState,观察编译期/运行时报错,理解"只算一次"。
小结
- RSC = 服务端跑组件 → flight payload(JSON + 引用)→ 客户端反序列化 → 正常渲染。
- 服务端
createRequest(777) /renderModel(3198) /emitModelChunk(4222)。 - 客户端
parseModel(5160) 还原元素树,resolveModel(2872) 加载客户端组件。 use client留引用、use server定义动作,边界决定谁在哪跑。- SSR 发 HTML,RSC 发数据,两者可叠加。
- 下一步:RSC 之上长出来的 Actions 和表单,第 31 章。