Skip to main content

第 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

服务端入口是 createRequestReactFlightServer.js:777),它建立一次请求上下文,然后开始渲染组件树。renderModel3198)负责把每个"模型"序列化:对象、数组、字符串、数字直接转 JSON;函数、类、React 元素这些 JSON 表达不了的东西,用特殊引用编码。最终的产物由 emitModelChunk4222)一块块发出去。

关键点:服务端组件在服务端真的跑了一遍renderElement2102),所以它能读数据库、算业务逻辑。跑完只留下"结果"(序列化数据),没有可执行代码发给客户端

flight 协议:JSON 加引用

序列化产物叫 flight payload,格式是"按 id 引用的模型块":

  • 基本类型(字符串、数字、布尔、数组、普通对象)→ 原样 JSON。
  • 客户端组件的引用 → 用一个特殊前缀($ 开头)编码成"模块引用":客户端知道这个"是个组件,但真正的实现在我这边某个模块里"。
  • 服务端组件的结果 → 已经展开成一棵元素树了,直接序列化。

所以这份 payload 是"数据和引用"的混合:数据是服务端算好的,引用是留给客户端"去取模块实现"的钩子。

客户端:反序列化,还原元素树

客户端拿到 payload,parseModelReactFlightClient.js:5160)按 id 把模型块反序列化:看到 $ 前缀的引用,就去加载对应的客户端组件模块(resolveModel2872),把它解析成真实的组件引用。最终拼出一棵完整的 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 干的事。

动手实验

  1. 看 flight payload:在 emitModelChunk(ReactFlightServer.js:4222)打断点,看序列化出的模型块长什么样,$ 引用的格式。
  2. 看客户端还原:在 parseModel(ReactFlightClient.js:5160)打断点,观察引用怎么被解析成组件。
  3. 验证服务端组件无状态:服务端组件里 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 章。