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:
- 展示静态内容
- 从数据库或文件系统获取数据
- 不需要交互的页面部分
- SEO 重要的内容
使用 Client Components:
- 需要交互(点击、输入等)
- 需要状态管理(useState、useReducer)
- 需要生命周期(useEffect)
- 使用浏览器 API(localStorage、window)
- 使用依赖客户端的第三方库
总结
React Server Components 代表了 React 的未来方向。理解它们的区别和组合方式,能帮助你构建更高效、更快速的 React 应用。核心原则:
- 默认使用 Server Components
- 只在需要交互时使用 Client Components
- 通过 children 模式组合两种组件
- 在 Server Components 中获取数据