React Server Components(RSC)Flight 协议负载格式与客户端选择性水合(Selective Hydration)底层原理是什么?

很多开发者误以为 React Server Components(RSC) 等同于传统的 SSR(服务端渲染 HTML)。事实上,RSC 与 SSR 是互补的两套机制:RSC 运行在 SSR 之前,其产出物不是 HTML 字符串,而是一种名为 React Flight 协议的流式行分隔序列化树(text/x-component)。

一、 RSC(Server Components)与传统 SSR 的本质区别

维度 React Server Components(RSC) 传统 SSR(Server-Side Rendering)
代码是否发送到浏览器 0 字节 JS 发往浏览器(服务端组件内部引用的 marked、highlight.js、数据库驱动永远不进客户端 Bundle) 组件代码必须同时打包进服务端与客户端 Bundle(以便客户端执行 Hydration 水合)
产出的数据格式 RSC Flight Payload(含虚拟 DOM 结构描述、客户端组件 Module 引用占位符 $L1 与流式 Promise 槽位) 纯 HTML 字符串流(<!DOCTYPE html>...)
客户端路由切换时 直接向服务端请求新页面的 Flight Payload 流,客户端 React 无缝合并更新虚拟 DOM 树,不销毁当前页面状态(如输入框焦点、视频播放进度) 传统整页刷新或客户端被迫全量走 CSR 拉取 JSON

二、 RSC Flight 协议真实报文结构拆解

当服务端渲染一棵包含客户端组件 <LikeButton /> 与异步 <Suspense> 的 RSC 树时,发往客户端的 Flight 流(在首屏 HTML 底部的 self.__next_f.push(...) 中可见)长这样:

// 第 1 行 (Chunk 1):声明客户端组件模块引用表(告诉打包器去加载哪个客户端 JS chunk)
1:I["app/components/LikeButton.tsx", ["app/page-chunk.js"], "LikeButton"]

// 第 2 行 (Chunk 0):根组件树结构。注意:
// - 服务端组件 <h1> 直接变成最终宿主标签 ["$","h1",null,{"children":"文章标题"}]
// - 客户端组件变成引用占位符 "$L1" 并附上序列化后的 Props {"initialLikes":42}
// - 尚未完成的慢速 Suspense 子树变成挂起占位符 "$2"
0:["$","main",null,{"children":[["$","h1",null,{"children":"文章标题"}],["$","$L1",null,{"initialLikes":42}],["$","$react.suspense",null,{"fallback":"加载评论中...","children":"$2"}]]}]

// 200ms 后慢接口返回,流式追加第 3 行 (Chunk 2),客户端自动将 "$2" 替换为真实评论列表!
2:["$","ul",null,{"children":[["$","li",null,{"children":"第一条评论"}]]}]

三、 客户端选择性水合(Selective Hydration)三大突破

配合 React 18/19 的 hydrateRoot 与 <Suspense>,客户端水合不再是阻塞主线程的单次同步重操作:

  1. 分块流式水合:HTML 流到达一块即可水合一块,无需等待底部的慢速 <Suspense> 块加载完成。
  2. 基于用户交互的优先级抢占(Interaction Re-prioritization):如果页面正在水合侧边栏 A,此时用户突然点击了评论区 B,React 捕获到原生点击事件后,会立即中断 A 的水合,优先同步完成 B 的水合,并重放(Replay)刚才那次点击事件!

同分类其他题目