server component 的整个渲染过程是怎么样的?在这个渲染过程中 server component 和 client component 有什么区别
React Server Component 的整个渲染过程
React Server Components(RSC)将组件分为两类:Server Component(只在服务器端运行)和 Client Component(在客户端运行)。整个渲染过程可以概括为:服务器端生成 RSC 数据流 → 客户端接收并重建组件树 → 挂载/水合 Client Component。
下面是完整流程(以典型的 React 18 + RSC 实现为例,不包含 Next.js 等框架的附加 SSR 阶段):
1. 服务器端渲染阶段
1.1 请求触发
客户端发起页面请求(如初次加载或导航到新路由)。服务器收到请求后,开始渲染根组件(通常是一个 Server Component)。
1.2 递归渲染 Server Component
服务器依次执行每个 Server Component 的代码:
- Server Component 可以直接使用
async/await读取数据库、文件系统或调用内部服务。 - 渲染过程中,如果遇到 另一个 Server Component,继续在服务器端执行它。
- 如果遇到 Client Component,服务器不会执行它的函数体,而是:
- 生成一个占位符节点,记录该 Client Component 的模块引用(
id)和可序列化的 props。 - 将子元素(可能继续包含 Server/Client Component)递归处理。
- 生成一个占位符节点,记录该 Client Component 的模块引用(
1.3 序列化为 RSC Payload
整个组件树最终被序列化为一种特殊的流式格式(RSC Payload),例如:
1:I{"id":"./src/ClientButton.js","chunks":["client-button"]}
2:{"name":"John"}
3:$
...
- Server Component 的输出被“压平”为原始 React 元素(如
<div>hello</div>)以及文本节点。 - Client Component 被编码为带有模块 ID 的引用,并附上其 props(必须可序列化)。
- 不包含任何实际的 HTML 字符串,而是一种描述性的数据结构。
1.4 发送给客户端
服务器将 RSC Payload 发送到客户端(可通过 HTTP 或流式传输)。
2. 客户端重建阶段
2.1 解析 Payload
客户端接收到 RSC Payload 后,React 反序列化并构建一个虚拟 React 元素树。
2.2 动态加载 Client Component 模块
当虚拟树中遇到一个 Client Component 的引用时,React 会:
- 根据模块 ID 动态导入对应的 JavaScript 包(如果尚未加载)。
- 将 props 传入该 Client Component。
2.3 最终渲染
- Server Component 部分(已经是纯 React 元素)被直接放入渲染树,不会在客户端再次“执行”组件函数。
- Client Component 在客户端正常执行(调用其函数体),生成 DOM 并挂载。
- 如果 Client Component 包含交互(如
onClick),后续还需要进行水合(Hydration),将事件监听器附加到已有的 DOM 节点上(但这一步通常和传统的 SSR 水合不同,RSC 中 Client Component 可能是动态插入的,水合由框架负责)。
3. 后续更新(客户端导航)
当用户在客户端导航(如点击 <Link>)到另一个 RSC 路由时:
- 不会刷新整个页面。
- 客户端向服务器发送请求,获取新页面的 RSC Payload。
- 服务器重新执行 Server Component(重新读取数据库等),生成新 Payload。
- 客户端合并新 Payload 与现有 Client Component,仅更新变化的 DOM。
Server Component 与 Client Component 在渲染过程中的主要区别
| 维度 | Server Component | Client Component |
|---|---|---|
| 执行环境 | 只在服务器端执行一次 | 在客户端执行(也可能在服务器端做预渲染/SSR,但不是 RSC 核心) |
| 代码包含 | 不会发送到客户端(减小 bundle 体积) | 打包发给客户端 |
| 访问能力 | 可直接访问数据库、文件系统、内部服务 | 只能通过 API 间接访问后端(或通过 props 接收数据) |
| 生命周期/hooks | ❌ 不能用 useState、useEffect、浏览器 API | ✅ 可用所有 React hooks 和浏览器 API |
| 异步支持 | 原生支持 async/await,直接在组件内 await fetch | 需要用 useEffect 或 Suspense + 数据 fetching 库 |
| 渲染产物 | 序列化的 React 元素描述(非 HTML) | 真实的 DOM 节点(或 React 元素) |
| 重新渲染 | 不会因客户端状态变化而重新渲染(除非重新请求整个页面/路由) | 会因 props/state 变化而正常 re-render |
| 交互性 | 无交互逻辑,只能作为静态内容或数据容器 | 可包含按钮、表单、事件处理等交互 |
补充说明(结合 SSR 的常见实现)
在实际框架(如 Next.js App Router)中,为了优化首屏加载体验,往往会额外增加一步 SSR 对 Client Component 的预渲染:
- 服务器先执行 RSC 流程获得组件树。
- 然后将树中的 Client Component 单独渲染为静态 HTML 并插入到响应中。
- 客户端首先显示这些 HTML,再加载 JS 进行水合。
这种混合模式中,上述“Server Component 只生成 Payload”的结论不完全成立(因为最终发送的 HTML 已包含 Client Component 的初始结构)。但从纯粹的 RSC 模型看,核心差异依然如表中所述。如果面试中需要严格区分,建议以标准 RSC 规范为准。