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 包。

数据获取的三种模式

在服务端组件中获取数据有以下几种常见模式:

  1. 组件内直接访问数据:最简单的方式。在 async 服务端组件中直接读取数据库或文件系统。
  2. 从父级服务端组件传递 props:布局或页面组件获取数据后向下传递,避免多个子组件需要同一数据时产生的瀑布式请求。
  3. 共享数据函数:使用 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 本身是框架无关的。
序列化导致服务端组件更慢 序列化格式紧凑且支持流式渐进传输,结合客户端不再需要数据请求的优势,实际体验几乎总是更快。

选择策略

一个实用的决策框架:需要读取数据库或文件系统?服务端组件。需要 useStateuseEffect 或事件处理?客户端组件。两者都需要?在服务端组件中获取数据,通过 props 传递给客户端组件。不确定?从服务端组件开始,这是默认选择,只有在遇到需要浏览器 API 的边界时才添加 "use client"。服务端组件不是对现有 React 模型的替代,而是一种扩展——思维模式从"一切都在浏览器中运行"转变为"组件在最适合的地方运行"。