Appearance
Server Components概念
学习目标
通过本章学习,你将掌握:
- Server Components的核心概念
- 服务端渲染的演进历史
- Server Components的工作原理
- 与传统SSR的区别
- Server Components的优势
- React 19中的Server Components
- 适用场景分析
- 架构设计思想
第一部分:什么是Server Components
1.1 基本概念
Server Components(服务器组件)是React 19引入的革命性特性,组件在服务器端渲染并流式传输到客户端,不会包含在客户端JavaScript bundle中。
jsx
// 这是一个Server Component(默认)
async function BlogPost({ id }) {
// 在服务器端执行
const post = await fetchPost(id); // 直接await,无需use()
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// 客户端收到的是HTML,不是JavaScript1.2 核心特点
1. 零客户端JavaScript
jsx
// Server Component
async function UserList() {
const users = await db.users.findMany();
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 打包后:
// ✅ 服务器代码:包含数据库查询
// ✅ 客户端bundle:0 KB(不包含这个组件)
// ✅ 传输内容:只有HTML2. 直接访问服务器资源
jsx
import { readFile } from 'fs/promises';
import { db } from '@/lib/database';
import { redis } from '@/lib/cache';
// Server Component可以直接使用Node.js API
async function ServerOnlyComponent() {
// 直接读取文件
const config = await readFile('./config.json', 'utf-8');
// 直接查询数据库
const users = await db.query('SELECT * FROM users');
// 直接访问缓存
const cached = await redis.get('data');
return <div>{/* 渲染数据 */}</div>;
}
// 这些代码永远不会发送到客户端!3. 自动代码分割
jsx
// Server Component
import HeavyLibrary from 'heavy-library'; // 1MB的库
async function DataProcessor() {
const data = await fetchData();
// 使用大型库处理数据
const processed = HeavyLibrary.process(data);
return <div>{processed}</div>;
}
// heavy-library不会包含在客户端bundle中!
// 只在服务器执行1.3 与Client Components的对比
jsx
// ========== Server Component(默认) ==========
// 文件:app/ServerComponent.jsx
async function ServerComponent() {
const data = await fetchData();
return <div>{data}</div>;
}
// 特点:
// - 在服务器运行
// - 可以async/await
// - 不能使用Hooks
// - 不能使用浏览器API
// - 不包含在客户端bundle
// ========== Client Component ==========
// 文件:app/ClientComponent.jsx
'use client'; // 必须声明
function ClientComponent() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击: {count}
</button>
);
}
// 特点:
// - 在客户端运行
// - 可以使用Hooks
// - 可以使用浏览器API
// - 包含在客户端bundle
// - 不能async/await(组件函数)第二部分:渲染历史演进
2.1 CSR(客户端渲染)
jsx
// 传统React应用(CSR)
function App() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
if (!data) return <div>Loading...</div>;
return <div>{data.content}</div>;
}
// 流程:
// 1. 下载HTML(几乎为空)
// 2. 下载JavaScript bundle
// 3. 执行React
// 4. 发起API请求
// 5. 显示内容
// 问题:
// ❌ 白屏时间长
// ❌ SEO不友好
// ❌ 首次加载慢2.2 SSR(服务端渲染)
jsx
// Next.js传统SSR
export async function getServerSideProps() {
const data = await fetchData();
return {
props: { data }
};
}
function Page({ data }) {
const [count, setCount] = useState(0);
return (
<div>
<div>{data.content}</div>
<button onClick={() => setCount(count + 1)}>
{count}
</button>
</div>
);
}
// 流程:
// 1. 服务器渲染HTML
// 2. 发送HTML到客户端(可见但不可交互)
// 3. 下载JavaScript bundle
// 4. Hydration(激活交互)
// 5. 完全可交互
// 问题:
// ❌ 全页面hydration(整个组件树都需要JS)
// ❌ 客户端bundle仍然很大
// ❌ 数据获取和渲染分离2.3 Server Components(React 19)
jsx
// Server Component(无需hydration)
async function BlogPost({ id }) {
// 在服务器直接获取数据
const post = await db.posts.findById(id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* 嵌入Client Component实现交互 */}
<LikeButton postId={post.id} />
</article>
);
}
// Client Component(只有这部分需要JS)
'use client';
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
);
}
// 流程:
// 1. 服务器渲染Server Component
// 2. 流式传输HTML
// 3. 只下载Client Components的JS
// 4. 只hydrate Client Components
// 5. 完全可交互
// 优势:
// ✅ 零客户端JS(Server Components部分)
// ✅ 流式渲染
// ✅ 自动代码分割
// ✅ 选择性hydration第三部分:工作原理
3.1 组件树分析
jsx
// 应用结构
<Page> {/* Server Component */}
<Header /> {/* Server Component */}
<Sidebar /> {/* Server Component */}
<MainContent> {/* Server Component */}
<Post /> {/* Server Component */}
<LikeButton /> {/* Client Component */}
<Comments /> {/* Server Component */}
<CommentForm /> {/* Client Component */}
</MainContent>
<Footer /> {/* Server Component */}
</Page>
// 渲染过程:
// 1. 服务器渲染所有Server Components
// 2. 生成HTML + Client Components占位符
// 3. 流式传输到客户端
// 4. 客户端只加载Client Components的JS
// 5. 只hydrate Client Components3.2 序列化格式
Server Components使用特殊的序列化格式(RSC Payload)传输到客户端:
javascript
// Server Component返回的不是HTML,而是序列化的React元素树
{
"type": "div",
"props": {
"children": [
{
"type": "h1",
"props": { "children": "Hello" }
},
{
"type": "@client/LikeButton", // Client Component引用
"props": { "postId": 123 }
}
]
}
}
// 客户端React接收这个结构并渲染
// Client Components会被替换为实际的组件代码3.3 数据流
jsx
// 数据流动方向
┌──────────────┐
│ 数据库 │
└──────┬───────┘
│
↓
┌──────────────┐
│Server Comp │ ← 在服务器获取数据
└──────┬───────┘
│
↓ (序列化)
┌──────────────┐
│ 网络传输 │
└──────┬───────┘
│
↓
┌──────────────┐
│ 客户端React │ ← 渲染Server Component的输出
└──────┬───────┘ + hydrate Client Components
│
↓
┌──────────────┐
│ 浏览器DOM │
└──────────────┘
// Server Component的数据获取在服务器完成
// 客户端只接收渲染结果3.4 刷新机制
jsx
// Server Components可以重新获取数据而无需完整页面刷新
'use client';
function RefreshButton() {
const router = useRouter();
const handleRefresh = () => {
// 触发Server Components重新渲染
router.refresh();
};
return (
<button onClick={handleRefresh}>
刷新数据
</button>
);
}
// 流程:
// 1. 用户点击刷新
// 2. 向服务器请求新的RSC Payload
// 3. Server Components重新执行(重新获取数据)
// 4. 客户端接收新的RSC Payload
// 5. React更新对应的DOM
// 6. Client Components的状态保持不变!第四部分:优势分析
4.1 性能优势
1. 更小的bundle
jsx
// 传统方式
import { format } from 'date-fns'; // 67 KB
import { marked } from 'marked'; // 45 KB
import hljs from 'highlight.js'; // 85 KB
function BlogPost({ post }) {
const formatted = format(new Date(post.date), 'PPP');
const html = marked(post.content);
const highlighted = hljs.highlightAuto(html);
return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}
// 客户端bundle: +197 KB
// Server Components方式
import { format } from 'date-fns'; // 只在服务器
import { marked } from 'marked';
import hljs from 'highlight.js';
async function BlogPost({ id }) {
const post = await fetchPost(id);
const formatted = format(new Date(post.date), 'PPP');
const html = marked(post.content);
const highlighted = hljs.highlightAuto(html);
return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}
// 客户端bundle: +0 KB!2. 更快的首屏
jsx
// 数据获取在服务器完成,无需等待客户端JavaScript
async function ProductPage({ id }) {
// 并行获取所有数据
const [product, reviews, recommendations] = await Promise.all([
fetchProduct(id),
fetchReviews(id),
fetchRecommendations(id)
]);
return (
<div>
<ProductDetail product={product} />
<Reviews reviews={reviews} />
<Recommendations items={recommendations} />
</div>
);
}
// 用户看到的:
// ✅ 立即显示完整内容(已经在服务器渲染)
// ✅ 无需等待JavaScript下载
// ✅ 无需等待客户端数据获取3. 流式渲染
jsx
// Server Components支持Suspense和流式传输
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
// 渲染流程:
// 1. 立即发送页面shell(骨架屏)
// 2. Header ready → 流式替换HeaderSkeleton
// 3. MainContent ready → 流式替换ContentSkeleton
// 4. Sidebar ready → 流式替换SidebarSkeleton
// 用户体验:渐进式显示内容,无需等待全部完成4.2 开发体验优势
1. 简化数据获取
jsx
// 传统方式:复杂的数据获取逻辑
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
Promise.all([
fetchUser(userId),
fetchPosts(userId)
]).then(([userData, postsData]) => {
setUser(userData);
setPosts(postsData);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
return <div>...</div>;
}
// Server Components:直观简洁
async function UserProfile({ userId }) {
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchPosts(userId)
]);
return <div>...</div>;
}2. 直接访问后端
jsx
// Server Component可以直接导入服务器代码
import { db } from '@/lib/database';
import { getCurrentUser } from '@/lib/auth';
import { sendEmail } from '@/lib/email';
async function Dashboard() {
// 直接访问数据库
const user = await getCurrentUser();
const stats = await db.stats.getUserStats(user.id);
// 直接发送邮件
if (stats.needsNotification) {
await sendEmail(user.email, 'Notification');
}
return <div>{/* 渲染数据 */}</div>;
}
// 无需创建API路由!
// 无需担心暴露敏感代码!3. 自动优化
jsx
// React自动优化Server Components
async function OptimizedComponent() {
const data = await fetchData();
return (
<div>
{/* React可以分析并优化这个组件 */}
{/* 自动代码分割、tree shaking等 */}
{data.items.map(item => (
<ItemCard key={item.id} item={item} />
))}
</div>
);
}4.3 安全优势
jsx
// 敏感代码只在服务器执行
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/database';
async function PaymentPage({ orderId }) {
// API密钥、数据库连接等敏感信息
// 永远不会暴露到客户端
const order = await db.orders.findById(orderId);
const paymentIntent = await stripe.paymentIntents.create({
amount: order.total,
currency: 'usd'
});
return (
<div>
<OrderSummary order={order} />
<PaymentForm clientSecret={paymentIntent.client_secret} />
</div>
);
}
// stripe密钥、数据库连接等
// 完全不会包含在客户端代码中第五部分:适用场景
5.1 适合Server Components
jsx
// ✅ 数据展示组件
async function ProductList({ category }) {
const products = await db.products.findByCategory(category);
return <ProductGrid products={products} />;
}
// ✅ 静态内容
async function BlogPost({ slug }) {
const post = await getPostBySlug(slug);
return <Article content={post.content} />;
}
// ✅ 服务器数据处理
async function Analytics() {
const stats = await calculateStats();
const reports = await generateReports(stats);
return <Dashboard data={reports} />;
}
// ✅ SEO关键内容
async function ProductPage({ id }) {
const product = await fetchProduct(id);
return (
<>
<title>{product.name}</title>
<meta name="description" content={product.description} />
<ProductDetail product={product} />
</>
);
}5.2 需要Client Components
jsx
// ✅ 交互式组件
'use client';
function InteractiveChart({ data }) {
const [selected, setSelected] = useState(null);
return (
<Chart
data={data}
onSelect={setSelected}
/>
);
}
// ✅ 使用浏览器API
'use client';
function GeolocationComponent() {
const [location, setLocation] = useState(null);
useEffect(() => {
navigator.geolocation.getCurrentPosition(pos => {
setLocation(pos.coords);
});
}, []);
return <Map location={location} />;
}
// ✅ 使用Hooks
'use client';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// ✅ 事件处理
'use client';
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
// 处理提交
};
return <form onSubmit={handleSubmit}>...</form>;
}注意事项
1. Server Components的限制
jsx
// ❌ 不能使用Hooks
async function BadServerComponent() {
const [state, setState] = useState(0); // 错误!
return <div>{state}</div>;
}
// ❌ 不能使用浏览器API
async function BadServerComponent() {
const width = window.innerWidth; // 错误!window未定义
return <div>{width}</div>;
}
// ❌ 不能使用事件处理
async function BadServerComponent() {
return (
<button onClick={() => alert('hi')}> {/* 错误!*/}
Click
</button>
);
}2. 组件边界清晰
jsx
// ✅ 明确区分Server和Client Components
// server-component.jsx
async function ServerComponent() {
const data = await fetchData();
return <ClientComponent data={data} />;
}
// client-component.jsx
'use client';
function ClientComponent({ data }) {
const [selected, setSelected] = useState(null);
return <div>{/* 使用data和state */}</div>;
}3. 数据序列化
jsx
// ❌ 不能传递函数
async function ServerComponent() {
const handleClick = () => console.log('click');
return <ClientComponent onClick={handleClick} />; // 错误!
}
// ✅ 传递可序列化的数据
async function ServerComponent() {
const data = { id: 1, name: 'Test' };
return <ClientComponent data={data} />; // 正确
}常见问题
Q1: Server Components会替代传统SSR吗?
A: 不是替代,而是增强。Server Components与SSR结合使用,提供更好的性能和开发体验。
Q2: 所有组件都应该是Server Components吗?
A: 不是。需要交互的部分应该是Client Components。
Q3: Server Components如何更新数据?
A: 通过路由刷新或Server Actions触发重新渲染。
总结
Server Components的核心价值
✅ 零客户端JavaScript
✅ 直接访问服务器资源
✅ 自动代码分割
✅ 更小的bundle
✅ 更快的首屏
✅ 更好的SEO
✅ 流式渲染
✅ 简化数据获取架构思想
传统React: 一切都在客户端
SSR: 服务器渲染 + 客户端hydration
Server Components: 服务器组件 + 选择性客户端组件
核心理念:
- 默认服务器渲染
- 按需客户端交互
- 最小化JavaScript传输
- 最大化性能Server Components是React 19最重要的创新,代表了Web开发的新方向!