vibcoding Team5 min read
React Server Components 深入解析:原理与最佳实践
深入理解 React Server Components 的工作原理,掌握 Server/Client Components 的正确使用方式。
#React#Server Components#Next.js#前端架构
React Server Components 深入解析:原理与最佳实践
什么是 React Server Components?
React Server Components (RSC) 是 React 18 引入的新范式,允许组件在服务端渲染,并直接发送 HTML 到客户端,无需发送对应的 JavaScript。
这与传统 SSR 不同:
- 传统 SSR: 服务端渲染 HTML,客户端再 hydrate(注水)整个应用
- RSC: 服务端渲染的组件永远不会发送 JS 到客户端
核心概念
Server Components vs Client Components
tsx
// Server Component (默认)
// ✅ 可以直接访问数据库
// ✅ 可以使用 fs、path 等 Node.js 模块
// ❌ 不能使用 useState、useEffect
// ❌ 不能使用浏览器 API
async function UserProfile({ userId }: { userId: string }) {
// 直接在组件中查询数据库
const user = await db.user.findUnique({ where: { id: userId } });
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Client Component
// ✅ 可以使用 hooks
// ✅ 可以使用浏览器 API
// ✅ 可以添加事件处理
// ❌ 不能直接访问数据库
'use client';
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
工作原理
1. 渲染流程
请求到达服务器
↓
React 渲染 Server Components
↓
生成 RSC Payload (序列化的组件树)
↓
发送到客户端
↓
React 解析 Payload,渲染 DOM
↓
Client Components hydrate
2. RSC Payload 是什么?
RSC Payload 是一种特殊的序列化格式,包含:
- Server Component 的渲染结果
- Client Component 的引用(不是代码本身)
- Props 数据
javascript
// 简化示例
{
"type": "div",
"children": [
{ "type": "h1", "children": "Hello World" },
{
"type": "$L1", // 引用 Client Component
"props": { "initialCount": 0 }
}
]
}
最佳实践
1. 组件边界设计
tsx
// ✅ 推荐:Server Component 包裹 Client Component
async function ProductPage({ id }: { id: string }) {
const product = await getProduct(id);
return (
<div>
{/* 静态内容在服务端渲染 */}
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* 交互组件是 Client Component */}
<AddToCartButton productId={id} />
</div>
);
}
// ❌ 避免:不必要地将整个页面变成 Client Component
'use client';
function ProductPage({ id }: { id: string }) {
const [product, setProduct] = useState(null);
useEffect(() => {
fetch(`/api/product/${id}`).then(...);
}, [id]);
// ...
}
2. 数据传递
tsx
// ✅ 将数据作为 props 传递给 Client Component
async function Dashboard() {
const stats = await getStats();
return (
<div>
<StatsSummary data={stats} /> {/* Server Component */}
<InteractiveChart data={stats} /> {/* Client Component */}
</div>
);
}
// ❌ 避免:在 Client Component 中重复获取数据
'use client';
function InteractiveChart() {
const [data, setData] = useState(null);
useEffect(() => {
// 这会导致额外的请求和加载延迟
fetch('/api/stats').then(...);
}, []);
}
3. 组合模式
tsx
// Server Component 可以渲染 Client Component
// Client Component 可以渲染 Server Component(通过 children)
// ✅ Slot Pattern
'use client';
function Modal({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
return (
<dialog open={isOpen}>
{/* children 可以是 Server Component */}
{children}
</dialog>
);
}
// 使用
async function Page() {
const data = await getData();
return (
<Modal>
{/* 这个内容在服务端渲染 */}
<ServerContent data={data} />
</Modal>
);
}
4. 避免的模式
tsx
// ❌ 不要在 Server Component 中导入 Client Component 并传递函数
async function Page() {
const handleClick = () => {
console.log('clicked');
};
return <Button onClick={handleClick} />; // 错误!函数不能序列化
}
// ✅ 正确做法:在 Client Component 中定义事件处理
'use client';
function Button() {
const handleClick = () => {
console.log('clicked');
};
return <button onClick={handleClick}>Click</button>;
}
性能优势
减少 JavaScript 体积
传统 CSR:
HTML (小) + JS (大) + 数据请求 + 渲染
传统 SSR:
HTML (大) + JS (大) + Hydration
RSC:
HTML (大) + JS (只有交互部分) + 部分 Hydration