React Server Components(RSC)代表了 React 数据获取与渲染方式的根本性变革。不同于以往在客户端挂载后再请求数据的模式,RSC 允许组件直接在服务端访问数据源——数据库、文件系统、内部 API——而无需将这些接口暴露到客户端代码中。
其核心架构思想在于服务器组件与客户端组件之间的边界划分。标记了 "use client" 的组件运行在浏览器中,可以使用 hooks、事件处理和浏览器 API;其余所有组件默认运行在服务端。服务端组件渲染为一种特殊格式,React 可以流式传输并渐进式水合。
组件树的层级架构
以典型的页面布局为例:
<Page> // 服务端组件
<Nav> // 服务端组件
<SearchBar /> // 客户端组件("use client")
</Nav>
<Content> // 服务端组件
<ProductList products={...}/> // 服务端组件(从数据库获取)
<AddToCartButton /> // 客户端组件(需要 onClick)
</Content>
</Page>
服务端组件永远不会被发送到浏览器。React 将其输出序列化为元素树,在需要交互性的位置嵌入客户端组件的"插槽"。客户端接收到的是一份混合体:服务端组件的预渲染 HTML,以及客户端组件的 JavaScript 包。
数据获取的三种模式
在服务端组件中获取数据有以下几种常见模式:
- 组件内直接访问数据:最简单的方式。在 async 服务端组件中直接读取数据库或文件系统。
- 从父级服务端组件传递 props:布局或页面组件获取数据后向下传递,避免多个子组件需要同一数据时产生的瀑布式请求。
- 共享数据函数:使用 React 的
cache()函数在同一次渲染中合并多个组件的重复请求。与数据加载器类似,但组合性更强。
// 服务端组件 — 仅在服务端运行
import {{ db }} from "./database";
async function ProductPage({{ id }}: {{ id: string }}) {{
const product = await db.product.findUnique({{ where: {{ id }} }});
const reviews = await db.review.findMany({{
where: {{ productId: id }},
take: 10,
}});
return (
<div>
<ProductDetail product={{product}} />
<ReviewList reviews={{reviews}} />
<AddReviewForm productId={{id}} />
{{/* 这个组件是客户端组件 */}}
</div>
);
}}
常见误区澄清
| 误区 | 事实 |
|---|---|
| 服务端组件将取代客户端组件 | 二者互为补充。服务端组件处理数据,客户端组件处理交互。 |
| 服务端组件中无法使用状态 | 状态存在于客户端组件中。页面可以混合两者——服务端负责数据,客户端负责状态。 |
| RSC 必须依赖特定框架 | React 团队提供了底层原语。Next.js App Router 是集成度最高的实现,但 API 本身是框架无关的。 |
| 序列化导致服务端组件更慢 | 序列化格式紧凑且支持流式渐进传输,结合客户端不再需要数据请求的优势,实际体验几乎总是更快。 |
选择策略
一个实用的决策框架:需要读取数据库或文件系统?服务端组件。需要 useState、useEffect 或事件处理?客户端组件。两者都需要?在服务端组件中获取数据,通过 props 传递给客户端组件。不确定?从服务端组件开始,这是默认选择,只有在遇到需要浏览器 API 的边界时才添加 "use client"。服务端组件不是对现有 React 模型的替代,而是一种扩展——思维模式从"一切都在浏览器中运行"转变为"组件在最适合的地方运行"。