说说对 React Server Components 的理解?

一、是什么

React Server Components(RSC)是一种在服务端执行并渲染的 React 组件。与传统的客户端组件不同,Server Components 的代码永远不会被下载到客户端,它们在服务端运行并将渲染结果以特殊的序列化格式发送给客户端。

RSC 的核心价值在于:组件可以直接访问服务端资源(数据库、文件系统、内部 API),同时其代码不会增加客户端的 JavaScript 包体积。

需要明确区分两个概念:

  • SSR(服务端渲染):在服务端将组件渲染为 HTML 字符串,客户端仍需下载所有 JS 并进行水合
  • RSC(服务端组件):组件本身在服务端执行,JS 代码不发送到客户端,输出的是序列化的组件树

二、'use client' 与 'use server' 指令

2.1 组件类型的划分

在 RSC 架构中,组件被分为两类:

Server Components(默认)
├── 在服务端执行
├── 可以 async/await
├── 可以直接访问数据库
├── 代码不进入客户端 bundle
└── 不能使用 state、effect、浏览器 API

Client Components('use client')
├── 在客户端执行(也可 SSR 预渲染)
├── 可以使用 state、effect
├── 可以响应用户交互
├── 代码包含在客户端 bundle
└── 不能直接访问服务端资源

2.2 'use client' 指令

在文件顶部添加 'use client' 声明该模块(及其导入的模块)为客户端组件:

'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

2.3 'use server' 指令

'use server' 标记一个函数为 Server Action,允许客户端组件调用服务端函数:

// actions.js
'use server';

import { db } from './database';

export async function createPost(formData) {
  const title = formData.get('title');
  const content = formData.get('content');

  await db.posts.create({ title, content });
}
'use client';

import { createPost } from './actions';

export function PostForm() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="标题" />
      <textarea name="content" placeholder="内容" />
      <button type="submit">发布</button>
    </form>
  );
}

三、Server Components 的优势

3.1 零客户端 JavaScript

Server Components 的代码不会出现在客户端 bundle 中。这意味着使用大型库不会影响客户端体积:

// Server Component - 这些 import 不会增加客户端 bundle 大小
import { marked } from 'marked';
import hljs from 'highlight.js';
import { format } from 'date-fns';

async function BlogPost({ slug }) {
  const post = await db.posts.findBySlug(slug);

  const htmlContent = marked(post.content, {
    highlight: (code, lang) => hljs.highlight(code, { language: lang }).value,
  });

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{format(post.createdAt, 'yyyy年MM月dd日')}</time>
      <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
    </article>
  );
}

3.2 直接访问后端资源

Server Components 可以直接访问数据库、读取文件系统,无需通过 API 层——直接 import Node.js 模块和数据库客户端即可。

3.3 流式渲染

Server Components 天然支持流式传输,配合 Suspense 实现渐进式页面加载:

async function Dashboard() {
  return (
    <div className="dashboard">
      <h1>仪表盘</h1>
      <div className="grid">
        <Suspense fallback={<CardSkeleton />}>
          <SalesCard />
        </Suspense>
        <Suspense fallback={<CardSkeleton />}>
          <UsersCard />
        </Suspense>
        <Suspense fallback={<ChartSkeleton />}>
          <RevenueChart />
        </Suspense>
      </div>
    </div>
  );
}

async function SalesCard() {
  const sales = await db.analytics.getSales();
  return (
    <div className="card">
      <h3>今日销售</h3>
      <p className="value">¥{sales.today.toLocaleString()}</p>
    </div>
  );
}

每个 Suspense 边界独立流式传输,数据先到的先显示。

四、Server Components 的限制

Server Components 不能使用任何客户端特性:

// Server Component 中以下操作均不允许
async function InvalidServerComponent() {
  const [state, setState] = useState(0);       // ❌ 无 state
  useEffect(() => {}, []);                      // ❌ 无 effect
  onClick={() => {}}                            // ❌ 无事件处理
  window.localStorage.getItem('key');           // ❌ 无浏览器 API
  useContext(SomeContext);                       // ❌ 无 context(可改用 use)
}

需要这些能力时,应将对应部分拆分为 Client Component。

五、Server Actions

Server Actions 是在 Server 端执行的异步函数,可以从客户端组件调用,类似于自动生成的 API 端点:

// actions.js
'use server';

import { db } from './db';
import { revalidatePath } from 'next/cache';

export async function toggleTodo(id) {
  const todo = await db.todos.findById(id);
  await db.todos.update(id, { completed: !todo.completed });
  revalidatePath('/todos');
}

export async function deleteTodo(id) {
  await db.todos.delete(id);
  revalidatePath('/todos');
}
'use client';

import { toggleTodo, deleteTodo } from './actions';
import { useTransition } from 'react';

export function TodoItem({ todo }) {
  const [isPending, startTransition] = useTransition();

  return (
    <li style={{ opacity: isPending ? 0.5 : 1 }}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => startTransition(() => toggleTodo(todo.id))}
      />
      <span>{todo.title}</span>
      <button onClick={() => startTransition(() => deleteTodo(todo.id))}>
        删除
      </button>
    </li>
  );
}

六、组合模式

6.1 Server → Client 组合(推荐)

Server Component 可以将 Client Component 作为子组件渲染,并传递可序列化的 props:

// Server Component
async function ProductPage({ id }) {
  const product = await db.products.findById(id);
  const reviews = await db.reviews.findByProduct(id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Client Component 接收序列化数据 */}
      <AddToCartButton productId={product.id} price={product.price} />
      <ReviewList initialReviews={reviews} productId={id} />
    </div>
  );
}
'use client';

export function AddToCartButton({ productId, price }) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => {
      addToCart(productId);
      setAdded(true);
    }}>
      {added ? '已添加' : `加入购物车 ¥${price}`}
    </button>
  );
}

6.2 通过 children 传递 Server Components

Client Component 可以通过 children 或其他 prop 接收 Server Component 的渲染结果。这是因为 Server Component 的渲染结果(序列化的 JSX)可以作为 prop 传递给 Client Component:

// Server Component
async function Layout() {
  const user = await getUser();
  return (
    <Sidebar>
      <ServerUserInfo user={user} />
    </Sidebar>
  );
}

// Sidebar 是 'use client',但 children 是 Server Component 的渲染结果

6.3 Client → Server 的限制

Client Component 不能直接 import Server Component:

'use client';

// ❌ 不允许:Client Component 不能导入 Server Component
import { ServerComponent } from './ServerComponent';

export function ClientWrapper() {
  return <ServerComponent />; // 这会使 ServerComponent 变成 Client Component
}

七、RSC 的传输格式

Server Components 不输出 HTML,而是输出一种特殊的序列化格式(RSC Payload),类似于 JSON 的流式编码。客户端 React 接收 payload 后,解析组件树、渲染 DOM、将 Client Component 占位符替换为交互组件并完成水合。

八、在 Next.js App Router 中的实践

Next.js App Router 是目前 RSC 最成熟的实现。在 App Router 中,所有组件默认是 Server Component,只有标记了 'use client' 的组件才是 Client Component:

// app/page.jsx - 默认 Server Component
export default async function HomePage() {
  const products = await db.products.findFeatured();
  return (
    <main>
      <SearchBar /> {/* 'use client' 组件 */}
      <ProductGrid products={products} /> {/* Server Component */}
    </main>
  );
}

九、总结

概念说明
Server Components服务端执行、零客户端 JS 的组件
Client Components带交互能力的客户端组件
'use client'声明客户端组件边界
'use server'声明 Server Action
Server Actions服务端函数,可从客户端直接调用
RSC Payload序列化的组件树传输格式
组合模式Server→Client 通过 props/children
核心优势小 bundle、直接访问后端、流式渲染

React Server Components 代表了 React 架构的一次范式转变——从"所有组件都在客户端运行"到"让每个组件在最适合它的环境运行"。它不是 SSR 的替代品,而是与 SSR 互补的全新架构层。