React Server Components:实用深入解析

React Server Components:实用深入解析

RSC 边界、流式渲染与客户端/服务端划分在 Next.js App Router 中如何工作——以及何时使用客户端组件。

2026年6月12日12 分钟阅读作者 Shehzad Asadullah

React Server Components 是自 Hooks 以来 React 生态中最重大的架构转变之一。与 Next.js App Router 配合使用时,它们允许默认在服务器上渲染组件,仅将序列化输出发送到客户端,并将客户端 JavaScript 保留给真正需要交互的部分。理解服务器组件与客户端组件的边界,对于在 2026 年构建快速、可维护的应用至关重要。

展示 React Server Components 在服务器与客户端边界之间数据流的示意图
Server Components 在服务器上获取数据并渲染,而 Client Components 在边界处处理交互性。

什么是 React Server Components?

React Server Components(常缩写为 RSC)是在请求期间或构建时仅在服务器上执行的组件。与传统服务端渲染(在客户端水合整个页面)不同,RSC 发送名为 React Flight payload 的紧凑序列化表示。客户端接收此 payload 并重建 UI,而无需下载组件源代码或其依赖项。

此模型为生产应用带来多项实际优势:

  • 减小包体积 — 仅服务器使用的依赖永远不会发送到浏览器。
  • 直接数据访问 — 组件可查询数据库和文件系统,无需通过 API 路由暴露凭据。
  • 自动代码分割 — 每个服务器组件边界都会形成自然的分割点。
  • 改善首字节时间(TTFB) — 流式传输允许在较慢数据解析时渲染页面外壳。

心智模型很简单:将服务器组件视为默认渲染层,将客户端组件视为散布在组件树中的可选交互孤岛。

理解 Next.js 中的 RSC 边界

在 Next.js App Router 中,app 目录内的每个文件都是 Server Component,除非在文件顶部添加 "use client" 指令。该指令创建客户端边界。该文件导入的所有内容都会成为客户端包的一部分,而作为 props 传入的子组件仍可通过称为组合(composition)的技术进行服务端渲染。

跨边界组合

最强大的模式之一是将服务器组件作为子组件传递给客户端组件。服务器组件先渲染,其输出被流式传输到客户端组件的插槽中。这避免了常见陷阱:仅因一小部分需要状态或事件处理程序就将整页标记为客户端组件。

// ServerComponent.tsx (no directive — runs on server)
async function ProductList() {
  const products = await db.query("SELECT * FROM products");
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

// ClientWrapper.tsx
"use client";
export function ClientWrapper({ children }: { children: React.ReactNode }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <div>
      <button onClick={() => setExpanded(!expanded)}>Toggle</button>
      {expanded && children}
    </div>
  );
}

// page.tsx (Server Component)
export default function Page() {
  return (
    <ClientWrapper>
      <ProductList />
    </ClientWrapper>
  );
}

请注意,ProductList 即使位于交互式包装器内部,也永远不会成为客户端组件。服务器渲染它、序列化结果,客户端接收完成的标记。

流式传输与 Suspense

Next.js 利用 React 的流式能力逐步向浏览器发送 HTML。当你用 <Suspense> 边界包裹异步服务器组件时,框架可立即显示回退 UI,同时在后台解析较慢的部分。

流式传输对具有异构数据源的页面特别有效。快速缓存层可能在毫秒内解析,而复杂聚合查询可能需要数秒。没有流式传输时,用户会盯着空白屏幕直到一切完成。有了流式传输,导航栏、布局和缓存内容会立即出现。

  • 在独立的数据获取区域周围放置 <Suspense> 边界。
  • 使用有意义的回退 UI — 骨架屏优于通用加载动画。
  • 嵌套边界以精细控制哪些内容先流式传输。
  • 结合 loading.tsx 文件实现路由级回退。

RSC 与流式传输的结合从根本上改变了你对页面加载性能的思考方式。你不是优化单一瀑布式请求,而是将页面设计为一系列可独立流式传输的片段。

何时使用 Client Components

尽管默认服务器优先,客户端组件仍然不可或缺。任何依赖浏览器 API、本地状态、副作用或事件处理程序的功能都需要客户端边界。目标不是消除客户端组件,而是最小化其占用范围。

在需要以下任一功能时使用 "use client"

  • 事件处理程序 — onClick、onChange、onSubmit 及类似的 DOM 交互。
  • React Hooks — useState、useEffect、useReducer、useContext 及依赖它们的自定义 hooks。
  • 仅浏览器 API — localStorage、geolocation、IntersectionObserver 和 Web Audio。
  • 第三方库 — 许多图表、地图和动画库假定运行在浏览器环境。

实用的启发式方法是尽可能将客户端边界下推到组件树深处。不要将整个仪表板页面标记为客户端组件,而是将交互式图表或筛选面板提取为小型客户端孤岛,让周围的布局、页眉和数据表保留在服务器端。

数据获取模式

服务器组件可在组件级别使用 async/await 直接获取数据。服务端读取无需 useEffect 或单独的数据获取库。Next.js 通过 fetch API 和 unstable_cache 辅助函数扩展了缓存语义。

// Direct async fetch in a Server Component
async function BlogPost({ slug }: { slug: string }) {
  const post = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 3600 },
  }).then((res) => res.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

对于变更操作,React 19 Actions 可与服务器组件自然集成。使用 "use server" 指令定义服务器操作,并从客户端表单调用它们,无需手动构建 API 端点。这以类型安全、就近放置的方式弥合了服务端渲染与服务端变更之间的鸿沟。

常见陷阱与最佳实践

刚接触 RSC 的团队常遇到重复性错误。从服务器向客户端组件传递不可序列化的值(如函数或类实例)会抛出运行时错误。在客户端组件中导入仅服务器模块会导致构建失败。将大型组件树标记为客户端组件则完全违背了该架构的初衷。

采用以下实践以保持正确方向:

  • 以服务器组件为默认,精准添加客户端边界。
  • 将数据获取与消费数据的组件就近放置。
  • 使用 TypeScript 在编译时捕获序列化问题。
  • 定期分析客户端包,确保服务器组件发挥应有作用。
  • 在代码库中记录边界决策,以便团队保持一致性。

React Server Components 并非银弹,但它提供了构建默认即快速应用的连贯模型。掌握边界、流式传输以及服务器-客户端组合模式,将在你使用 Next.js App Router 交付的每个项目中带来回报。

喜欢这篇文章吗?

有项目或想法吗?我很乐意听你聊聊。

联系我