React 学习站
React›前沿与实践›前沿

React Server Components

前沿前沿与实践

一句话定义

Server Component 是只在服务端运行、不进客户端 bundle、渲染结果以序列化流的形式发给客户端的组件;与之相对,Client Component('use client')拥有状态与浏览器 API。两者可以在同一棵树里穿插组合。

为什么重要

RSC 改变了「React 组件默认在哪里执行」:数据获取移回服务端(消除客户端瀑布请求)、重型依赖不再打进 JS 包、首屏从「等 JS 加载」变成「流式送达」。这是 2024 年以来前端架构最大的范式迁移,Next.js App Router、RedwoodJS 等均以它为地基。

前置知识

  • kp-003 组件树与组合。
  • kp-013 children 组合(跨边界传内容的核心机制)。
  • kp-020 Suspense(流式渲染的载体)。

核心概念

  • 默认即 Server:Next.js App Router 中组件默认是 Server Component;需要交互才标 'use client'。
  • 能力边界:Server 端有——直连数据库/文件/密钥、async/await 取数;没有——useState/useEffect/useRef、事件处理器、浏览器 API。
  • Client Component 不是「服务端渲染的组件」:它也会在服务端做 SSR(产出 HTML),但之后还要在浏览器 hydrate 并常驻运行;RSC 的输出则是一次性的序列化树。
  • props 序列化规则:跨边界只能传「可序列化」的值(字符串/数字/纯对象/数组);函数、类实例不可传;children 可以——因为传的是「已渲染的服务端产物」。
  • 流式 + Suspense:服务端逐块发送已就绪的 UI,配合客户端 Suspense 边界渐进显示。

原理 / 机制

一棵混合树与数据流:

jsx// page.jsx —— Server Component(默认)
import { db } from './db';
import LikeButton from './LikeButton';

export default async function PostPage({ params }) {
  const post = await db.post.find(params.id);   // 直接查库,无需 API 中转
  const related = await db.post.related(post);  // 两个请求并行/按需

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      {/* Server → Client 边界:只能传可序列化 props */}
      <LikeButton postId={post.id} initialCount={post.likes} />
      {/* children 穿越边界:传的是"已渲染的 Server UI" */}
      <Sidebar>{await renderRelated(related)}</Sidebar>
    </article>
  );
}

// LikeButton.jsx —— Client Component
'use client';
import { useState, useOptimistic } from 'react';

export default function LikeButton({ postId, initialCount }) {
  const [count, setCount] = useState(initialCount);
  return <button onClick={() => setCount(c => c + 1)}>👍 {count}</button>;
}

渲染管线:

text请求 → 服务端执行 Server Components(可 await 数据)
     → 产出 RSC 载荷(序列化树,含 Client Component 的"占位引用")
     → 流式发送:HTML 骨架 + RSC 载荷
     → 客户端按 Suspense 边界渐进显示;Client Component hydrate
     → 后续导航:只更新 RSC 载荷,不刷新整页

收益对照(同一个「产品列表页」):

维度纯 SPARSC
数据获取浏览器先载 JS → 再发 API → 再渲染(瀑布)服务端取数随 HTML 一起送达
首屏 JS全部组件与依赖进 bundleServer 组件 0 KB,只有交互部分进包
markdown/语法高亮等重库打进客户端只在服务端运行,不进包
SEO需 SSR/SSG 配置天然 HTML

直观类比

SPA 像宜家自提:网页先下载整套说明书和零件(JS bundle),你在家自己组装(客户端渲染)。SSR 像快递半成品家具:到货就能用,但你还得自己拧最后一遍螺丝(hydrate)。RSC 像全屋定制:工厂(服务端)直接把装好的柜子(序列化 UI)送上门,你只需对「带电的部分」(交互组件)做接线——电钻(交互 JS)只买真正需要的那几件。

实例 / 案例

「Server 拉数据 + Client 处理交互」的搜索页:

jsx// app/search/page.jsx(Server)
export default async function SearchPage({ searchParams }) {
  const results = await searchProducts(searchParams.q); // 服务端取数
  return <SearchResults items={results} />;
}

// components/SearchBox.jsx(Client)
'use client';
export default function SearchBox({ initial }) {
  const router = useRouter();               // 交互在客户端
  const [q, setQ] = useState(initial);
  return (
    <form onSubmit={e => {
      e.preventDefault();
      router.push(`/search?q=${encodeURIComponent(q)}`); // 回到服务端取新数据
    }}>
      <input value={q} onChange={e => setQ(e.target.value)} />
    </form>
  );
}

注意心智转变:交互组件不再自己 fetch,而是「把查询参数写回 URL」,由服务端重新渲染——单向数据流上升到了「URL 即状态」的层面。

常见误区

  • 「RSC 就是 SSR」:SSR 是「服务端产出首屏 HTML + 客户端 hydrate 全部组件」;RSC 是「组件常驻服务端,产物序列化下发」。两者可以叠加。
  • 给 Server Component 加 onClick/useState:直接报错或被静默忽略;交互部分拆成 'use client' 子组件。
  • 跨边界传函数/复杂对象:序列化失败;传 id 让子组件自取,或用 Actions(kp-025)传递「可调用的服务端函数引用」。
  • 把整个页面标 'use client':边界应尽量下沉到「叶子交互组件」,让数据拉取与静态部分留在服务端。
  • 忘记 Client Component 也做 SSR:以为 'use client' 意味着「不服务端渲染」——它只是意味着「会在客户端运行/有状态」。

自测题

  1. 列出 Server Component 能做与不能做的各三件事。
  2. 「'use client' 的组件不做 SSR」错在哪?
  3. 为什么 children 能跨越 RSC 边界而函数不能?用序列化解释。
  4. 把「产品详情页」拆成 Server/Client 两部分的划分依据是什么?给出你的组件清单。
  5. RSC 相对纯 SPA 的两个数量级收益是什么?

与其他知识点的关系

  • 向前:组合模式(kp-013)与 Suspense(kp-020)是它的语法基础。
  • 向后:kp-025 的 Server Actions 是 RSC 体系的「上行通道」;kp-026 是它的落地框架;kp-019 的首屏优化问题在 RSC 下被部分结构性解决。

延伸阅读

  • Server Components(官方 RSC 指南):<https://react.dev/reference/rsc/server-components>
  • Next.js Server Components:<https://nextjs.org/docs/app/building-your-application/rendering/server-components>
  • RSC 解释文(Dan Abramov):<https://github.com/reactwg/server-components/discussions/5>