返回博客

React Server Components 深入解析

React Server Components(RSC)是 React 19 的核心特性,也是 Next.js App Router 的基础。它彻底改变了我们构建 React 应用的方式,将组件分为服务端和客户端两种类型,各自有不同的职责和限制。

核心概念:Server Components 在服务端渲染,不发送 JavaScript 到客户端;Client Components 在客户端渲染,可以使用状态和交互。

一、Server Components vs Client Components

理解两种组件的区别是掌握 RSC 的第一步:

特性 Server Components Client Components
渲染位置 服务端 客户端(浏览器)
JavaScript 发送 不发送(仅 HTML) 发送到客户端
数据获取 直接访问数据库、文件系统 需要通过 API 或 props
状态管理 不能使用 useState、useEffect 可以使用所有 Hooks
交互事件 不能使用 onClick 等事件 可以使用所有事件
导入限制 不能导入 Client Components 的库 可以导入任何组件

二、Server Components 的优势

1. 更小的客户端 JavaScript 包

Server Components 的代码永远不会发送到客户端。这意味着:

// Server Component - 代码不发送到客户端
async function ProductList() {
  // 直接查询数据库
  const products = await db.products.findMany();
  
  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

上面的组件中,数据库连接代码、查询逻辑都不会出现在客户端 bundle 中。

2. 直接访问后端资源

// Server Component 可以直接读取文件
import { readFile } from 'fs/promises';

async function MarkdownPost({ slug }) {
  const content = await readFile(`./posts/${slug}.md`, 'utf-8');
  return <article>{content}</article>;
}

// 直接访问数据库
async function UserProfile({ id }) {
  const user = await prisma.user.findUnique({ where: { id } });
  return <div>{user.name}</div>;
}

3. 自动代码分割

Client Components 会自动进行代码分割,只有需要时才加载:

// Server Component
import { Suspense } from 'react';

function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyChart /> {/* Client Component - 懒加载 */}
    </Suspense>
  );
}

三、如何标记 Client Components

在文件顶部添加 'use client' 指令:

'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      点击次数: {count}
    </button>
  );
}
注意'use client' 必须在文件最顶部,在所有 import 之前。标记后,该文件及其导入的所有模块都会被视为客户端代码。

四、组件组合模式

Server Component 导入 Client Component

// Server Component
import { Counter } from './Counter'; // Client Component

function Page() {
  return (
    <div>
      <h1>服务端标题</h1>
      <Counter initialCount={0} /> {/* 交互部分 */}
    </div>
  );
}

Client Component 导入 Server Component

这是不允许的!Client Component 不能直接导入 Server Component:

// ❌ 错误:Client Component 不能导入 Server Component
'use client';
import { ServerComponent } from './ServerComponent'; // 错误!

function ClientComponent() {
  return <ServerComponent />; // 报错
}

解决方案:通过 children props 传递:

// ✅ 正确:通过 children 传递
'use client';

function ClientComponent({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>切换</button>
      {isOpen && children}
    </div>
  );
}

// Server Component 作为父组件传递
function Page() {
  return (
    <ClientComponent>
      <ServerComponent /> {/* 通过 children 传递 */}
    </ClientComponent>
  );
}

五、数据获取最佳实践

在 Server Components 中直接获取

// 推荐:Server Component 直接获取
async function ProductPage({ id }) {
  const product = await fetchProduct(id);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

并行数据获取

// 并行获取多个数据源
async function Dashboard() {
  // 同时发起请求,不等待
  const usersPromise = fetchUsers();
  const ordersPromise = fetchOrders();
  const statsPromise = fetchStats();
  
  // 并行等待
  const [users, orders, stats] = await Promise.all([
    usersPromise,
    ordersPromise,
    statsPromise
  ]);
  
  return (
    <div>
      <UserList users={users} />
      <OrderList orders={orders} />
      <StatsPanel stats={stats} />
    </div>
  );
}

使用 Suspense 流式渲染

// 流式渲染:先显示快的内容,慢的内容后加载
function Page() {
  return (
    <div>
      <FastContent /> {/* 立即显示 */}
      
      <Suspense fallback={<Skeleton />}>
        <SlowData /> {/* 等数据就绪后显示 */}
      </Suspense>
    </div>
  );
}

六、常见陷阱

1. 在 Server Component 中使用 useState

// ❌ 错误
function ServerComponent() {
  const [state, setState] = useState(); // 报错!
  return <div />;
}

2. 在 Server Component 中使用事件处理

// ❌ 错误
function ServerComponent() {
  return <button onClick={() => {}}>点击</button>; // 报错!
}

3. 传递函数 props 给 Client Component

// ❌ 错误:函数不能从 Server Component 传递
function Page() {
  const handleClick = () => {}; // 服务端定义的函数
  return <ClientButton onClick={handleClick} />; // 报错!
}

// ✅ 正确:在 Client Component 中定义
'use client';
function ClientButton() {
  const handleClick = () => {};
  return <button onClick={handleClick}>点击</button>;
}
可传递的 props:Server Components 可以向 Client Components 传递序列化数据(字符串、数字、数组、对象),但不能传递函数、类实例等非序列化数据。

七、何时使用哪种组件

使用 Server Components

使用 Client Components

总结

React Server Components 代表了 React 的未来方向。理解它们的区别和组合方式,能帮助你构建更高效、更快速的 React 应用。核心原则: