Appearance
Server Components原理 - React服务端组件深度解析
1. Server Components概述
1.1 核心概念
typescript
const serverComponentsConcept = {
定义: '在服务端渲染并序列化的React组件',
类型: {
ServerComponent: '只在服务端运行',
ClientComponent: '在客户端运行',
SharedComponent: '可在两端运行'
},
优势: [
'零客户端JavaScript',
'直接访问后端资源',
'减少bundle大小',
'更好的性能',
'自动代码分割'
],
特点: {
async: '可以是async函数',
直接数据库: '直接查询数据库',
服务端only: '不发送到客户端',
无状态: '不能使用useState/useEffect'
}
};1.2 RSC vs SSR
typescript
const rscVsSSR = {
SSR: {
定义: 'Server-Side Rendering服务端渲染',
过程: '服务端生成HTML',
发送: 'HTML + JavaScript',
客户端: '需要hydration',
bundle: '包含所有组件代码',
示例: 'Next.js getServerSideProps'
},
RSC: {
定义: 'React Server Components',
过程: '服务端执行组件',
发送: '序列化的组件树',
客户端: '无需hydration(Server Component部分)',
bundle: '只包含Client Component',
示例: 'async function Component()'
},
组合使用: `
SSR + RSC:
1. Server Component在服务端执行
2. 生成Virtual DOM
3. 序列化发送到客户端
4. Client Component在客户端hydration
优势: 两者结合,最佳性能
`
};1.3 组件类型标记
typescript
// 标记Server Component
// app/ServerComponent.server.tsx
export default async function ServerComponent() {
const data = await fetchFromDatabase();
return <div>{data}</div>;
}
// 标记Client Component
// app/ClientComponent.client.tsx
'use client';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// 文件命名约定
const namingConventions = {
'.server.tsx': 'Server Component',
'.client.tsx': 'Client Component',
'.tsx': 'Shared Component (默认Server)',
或使用指令: {
'use client': '标记为Client Component',
'use server': '标记为Server Component (默认)'
}
};2. Server Components工作原理
2.1 渲染流程
typescript
const renderingFlow = {
步骤: [
'1. 客户端请求页面',
'2. 服务端执行Server Components',
'3. 获取数据(数据库/API)',
'4. 渲染组件树',
'5. 序列化为RSC Payload',
'6. 发送到客户端',
'7. 客户端重建组件树',
'8. Client Components hydration'
],
示例: `
// 服务端
async function Page() {
const user = await db.user.findOne({ id: 1 });
return (
<div>
<h1>{user.name}</h1>
<ClientCounter /> {/* Client Component */}
</div>
);
}
// 流程:
// 1. 执行Page()
// 2. await db.user.findOne() - 在服务端执行
// 3. 渲染JSX
// 4. 序列化结果
// 5. 发送到客户端
// 6. 客户端接收并渲染
// 7. ClientCounter进行hydration
`
};2.2 RSC Payload格式
typescript
// RSC序列化格式
const rscPayload = {
示例: `
// 服务端组件
async function ServerComponent() {
const data = await fetchData();
return (
<div>
<h1>{data.title}</h1>
<ClientComponent value={data.value} />
</div>
);
}
// 序列化为RSC Payload
M1:{"id":"./ClientComponent.client.tsx","chunks":["client1"],"name":""}
J0:["$","div",null,{"children":[
["$","h1",null,{"children":"Title"}],
["$","@1",null,{"value":42}]
]}]
`,
格式说明: {
M行: '模块引用(Client Component)',
J行: 'JSON数据',
$: '元素标记',
'@数字': '引用模块'
},
传输: {
格式: '流式JSON',
优势: '可以边生成边发送',
压缩: 'gzip压缩',
大小: '比HTML更小'
}
};2.3 数据流
typescript
const dataFlow = {
服务端到客户端: `
// Server Component
async function ProductList() {
// 在服务端执行
const products = await db.products.findMany();
// props传递给Client Component
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Client Component
'use client';
function ProductCard({ product }) {
const [liked, setLiked] = useState(false);
return (
<div>
<h3>{product.name}</h3>
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
</div>
);
}
// 数据流:
// 1. 服务端查询products
// 2. 将数据序列化为props
// 3. 传递给Client Component
// 4. Client Component在客户端渲染
`,
限制: [
'props必须可序列化',
'不能传递函数',
'不能传递类实例',
'可以传递Promise(use Hook读取)'
]
};3. Server Component特性
3.1 直接访问后端资源
typescript
const backendAccess = {
数据库访问: `
// Server Component可以直接查询数据库
import { db } from '@/lib/database';
async function UserProfile({ userId }) {
// 直接数据库查询
const user = await db.user.findUnique({
where: { id: userId },
include: { posts: true }
});
return (
<div>
<h1>{user.name}</h1>
<ul>
{user.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
`,
文件系统: `
import fs from 'fs/promises';
import path from 'path';
async function MarkdownPage({ slug }) {
// 读取文件系统
const filePath = path.join(process.cwd(), 'content', \`\${slug}.md\`);
const content = await fs.readFile(filePath, 'utf-8');
return <Markdown content={content} />;
}
`,
环境变量: `
async function Config() {
// 直接使用服务端环境变量
const apiKey = process.env.SECRET_API_KEY;
const data = await fetch('https://api.example.com', {
headers: { 'Authorization': \`Bearer \${apiKey}\` }
});
return <div>{data.result}</div>;
}
// 优势:
// - apiKey不会暴露给客户端
// - 不需要API route
`
};3.2 零Bundle影响
typescript
const zeroBundleImpact = {
示例: `
// Server Component
import { marked } from 'marked'; // 大型库
import { highlight } from 'highlight.js'; // 代码高亮库
async function Article({ slug }) {
const markdown = await getArticleContent(slug);
// 这些库只在服务端运行
const html = marked(markdown);
const highlighted = highlight(html);
return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}
// 优势:
// - marked和highlight.js不会打包到客户端
// - 客户端bundle减小了约100KB
// - 更快的加载和执行
`,
bundle大小对比: {
传统: '包含所有依赖,bundle很大',
RSC: '只包含Client Component,bundle小得多'
}
};3.3 自动代码分割
typescript
const automaticCodeSplitting = {
原理: `
Server Component引用Client Component时,
React自动进行代码分割
`,
示例: `
// Server Component
async function Page() {
return (
<div>
<Header /> {/* 自动分割 */}
<Sidebar /> {/* 自动分割 */}
<MainContent /> {/* 自动分割 */}
<Footer /> {/* 自动分割 */}
</div>
);
}
// 每个Client Component自动成为一个chunk
`,
优势: [
'无需手动配置',
'按需加载',
'减少初始bundle',
'更快的首屏'
]
};4. Client Component特性
4.1 use client指令
typescript
const useClientDirective = {
用法: `
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
);
}
`,
作用: [
'标记为Client Component',
'可以使用Hooks',
'可以使用浏览器API',
'可以有交互性'
],
边界: `
'use client'标记的文件及其所有导入都是Client Component
// ClientComponent.tsx
'use client';
import { Helper } from './helper'; // helper也是Client
export function ClientComponent() {
const helper = new Helper(); // Helper在客户端执行
return <div>{helper.value}</div>;
}
`
};4.2 组件组合
typescript
const componentComposition = {
Server中使用Client: `
// ✓ 正确:Server Component可以导入Client Component
// ServerPage.tsx
import ClientCounter from './ClientCounter'; // 'use client'
async function ServerPage() {
const data = await fetchData();
return (
<div>
<h1>{data.title}</h1>
<ClientCounter />
</div>
);
}
`,
Client中使用Server: `
// ❌ 错误:Client Component不能直接导入Server Component
'use client';
import ServerComponent from './ServerComponent';
function ClientComponent() {
return <ServerComponent />; // 错误!
}
// ✓ 正确:通过children prop
'use client';
function ClientLayout({ children }) {
return <div className="layout">{children}</div>;
}
// Server Component
function Page() {
return (
<ClientLayout>
<ServerComponent /> {/* 作为children传入 */}
</ClientLayout>
);
}
`,
组合模式: `
// Server Component
async function Page() {
const data = await fetchData();
return (
<ClientProvider initialData={data}>
<ServerContent />
</ClientProvider>
);
}
// Client Component (Provider)
'use client';
function ClientProvider({ children, initialData }) {
const [data, setData] = useState(initialData);
return (
<DataContext.Provider value={{ data, setData }}>
{children}
</DataContext.Provider>
);
}
// Server Component (Content)
async function ServerContent() {
const moreData = await fetchMoreData();
return <div>{moreData}</div>;
}
`
};5. 数据获取
5.1 服务端数据获取
typescript
const serverDataFetching = {
直接async: `
// Server Component
async function UserProfile({ userId }) {
// 直接await
const user = await db.user.findUnique({
where: { id: userId }
});
const posts = await db.post.findMany({
where: { authorId: userId },
orderBy: { createdAt: 'desc' },
take: 10
});
return (
<div>
<h1>{user.name}</h1>
<PostList posts={posts} />
</div>
);
}
`,
并行请求: `
async function Page() {
// 并行获取数据
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return (
<div>
<UserInfo user={user} />
<Posts posts={posts} />
<Comments comments={comments} />
</div>
);
}
`,
Suspense集成: `
function Page() {
return (
<div>
<Suspense fallback={<UserSkeleton />}>
<UserProfile userId={1} />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<UserPosts userId={1} />
</Suspense>
</div>
);
}
// 每个Suspense边界独立加载
`
};5.2 客户端数据获取
typescript
const clientDataFetching = {
use_Hook: `
'use client';
import { use } from 'react';
function ClientComponent({ dataPromise }) {
// 读取Promise
const data = use(dataPromise);
return <div>{data.title}</div>;
}
// Server Component传递Promise
async function ServerPage() {
const dataPromise = fetchData();
return (
<Suspense fallback={<Loading />}>
<ClientComponent dataPromise={dataPromise} />
</Suspense>
);
}
`,
传统方式: `
'use client';
function ClientComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData().then(result => {
setData(result);
setLoading(false);
});
}, []);
if (loading) return <Loading />;
return <div>{data.title}</div>;
}
`
};6. 实战应用
6.1 博客系统
typescript
// 文章列表页(Server Component)
async function BlogPage() {
const articles = await db.article.findMany({
orderBy: { publishedAt: 'desc' },
take: 20
});
return (
<div>
<h1>Blog Articles</h1>
{articles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
);
}
// 文章卡片(Server Component)
async function ArticleCard({ article }) {
const author = await db.user.findUnique({
where: { id: article.authorId }
});
return (
<article>
<h2>{article.title}</h2>
<p>{article.excerpt}</p>
<div>By {author.name}</div>
<LikeButton articleId={article.id} />
</article>
);
}
// 点赞按钮(Client Component)
'use client';
function LikeButton({ articleId }) {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(0);
const handleLike = async () => {
setLiked(!liked);
setCount(c => liked ? c - 1 : c + 1);
await fetch(\`/api/articles/\${articleId}/like\`, {
method: 'POST'
});
};
return (
<button onClick={handleLike}>
{liked ? '❤️' : '🤍'} {count}
</button>
);
}6.2 仪表板应用
typescript
// 仪表板(Server Component)
async function Dashboard() {
// 并行获取数据
const [stats, recentOrders, topProducts] = await Promise.all([
fetchStats(),
fetchRecentOrders(),
fetchTopProducts()
]);
return (
<div className="dashboard">
<StatsCards stats={stats} />
<div className="grid">
<Suspense fallback={<ChartSkeleton />}>
<SalesChart />
</Suspense>
<Suspense fallback={<ListSkeleton />}>
<RecentOrders orders={recentOrders} />
</Suspense>
</div>
<Suspense fallback={<GridSkeleton />}>
<TopProducts products={topProducts} />
</Suspense>
</div>
);
}
// 销售图表(Client Component)
'use client';
function SalesChart() {
const data = use(fetchSalesData());
return <Chart data={data} type="line" />;
}
// 订单列表(混合)
async function RecentOrders({ orders }) {
return (
<div>
<h2>Recent Orders</h2>
{orders.map(order => (
<OrderRow key={order.id} order={order} />
))}
</div>
);
}
'use client';
function OrderRow({ order }) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<div onClick={() => setExpanded(!expanded)}>
Order #{order.id} - ${order.total}
</div>
{expanded && <OrderDetails orderId={order.id} />}
</div>
);
}7. 性能优化
7.1 流式渲染
typescript
const streamingRendering = {
原理: `
Server Component支持流式渲染:
1. 服务端边生成边发送
2. 客户端边接收边渲染
3. 不需要等待所有数据
`,
示例: `
async function Page() {
return (
<div>
{/* 立即渲染 */}
<Header />
{/* 异步加载 */}
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
{/* 立即渲染 */}
<Footer />
</div>
);
}
async function SlowComponent() {
// 慢速数据获取
await new Promise(resolve => setTimeout(resolve, 3000));
const data = await fetchData();
return <div>{data}</div>;
}
// 渲染流程:
// 1. 立即发送Header
// 2. 发送Skeleton
// 3. 发送Footer
// 4. SlowComponent完成后发送更新
`
};7.2 选择性Hydration
typescript
const selectiveHydration = {
原理: '只对需要交互的部分进行hydration',
示例: `
function Page() {
return (
<div>
{/* Server Component - 不需要hydration */}
<StaticContent />
{/* Client Component - 需要hydration */}
<InteractiveWidget />
{/* Server Component - 不需要hydration */}
<Footer />
</div>
);
}
`,
优势: [
'减少hydration时间',
'减少JavaScript执行',
'更快的TTI(Time to Interactive)',
'更好的性能'
]
};8. 限制和注意事项
8.1 Server Component限制
typescript
const serverComponentLimitations = {
不能使用: [
'useState',
'useEffect',
'useContext (需要Provider)',
'浏览器API',
'事件处理器',
'createContext'
],
可以使用: [
'async/await',
'服务端API',
'直接数据库访问',
'文件系统',
'环境变量'
],
示例: `
// ❌ 错误
async function ServerComponent() {
const [count, setCount] = useState(0); // 错误!
useEffect(() => { // 错误!
console.log('mounted');
}, []);
return (
<button onClick={() => setCount(c => c + 1)}> {/* 错误! */}
{count}
</button>
);
}
// ✓ 正确
async function ServerComponent() {
const data = await fetchData(); // 正确
const file = await fs.readFile('file.txt'); // 正确
return <div>{data}</div>;
}
`
};8.2 Client Component限制
typescript
const clientComponentLimitations = {
不能直接: [
'访问数据库',
'读取文件系统',
'使用服务端专属模块',
'导入Server Component'
],
可以: [
'使用所有Hooks',
'处理用户交互',
'使用浏览器API',
'接收Server Component作为children'
],
示例: `
'use client';
// ❌ 错误
import { db } from '@/lib/database'; // 错误!服务端模块
function ClientComponent() {
const data = db.query(); // 错误!
return <div>{data}</div>;
}
// ✓ 正确
function ClientComponent({ data }) {
const [state, setState] = useState(data); // 正确
return (
<div onClick={() => setState(/* ... */)}> {/* 正确 */}
{state}
</div>
);
}
`
};9. 最佳实践
typescript
const bestPractices = {
组件划分: [
'默认使用Server Component',
'只在需要交互时使用Client Component',
'尽可能推迟Client边界',
'将Client Component放在叶子节点'
],
数据获取: [
'在Server Component中直接获取',
'避免客户端数据获取',
'使用Suspense处理加载状态',
'并行获取多个数据源'
],
性能: [
'减少Client Component数量',
'使用流式渲染',
'利用Suspense边界',
'预加载关键资源'
],
示例: `
// ✓ 好的结构
async function Page() { // Server
const data = await fetchData();
return (
<Layout> {/* Client */}
<ServerContent data={data} /> {/* Server */}
</Layout>
);
}
'use client';
function Layout({ children }) {
// 交互逻辑
return <div className="layout">{children}</div>;
}
async function ServerContent({ data }) {
const processed = await processData(data);
return (
<div>
{processed.map(item => (
<ItemCard key={item.id} item={item} />
))}
</div>
);
}
`
};10. 面试高频问题
typescript
const interviewQA = {
Q1: {
question: 'Server Components是什么?',
answer: [
'1. 只在服务端运行的React组件',
'2. 零客户端JavaScript',
'3. 直接访问后端资源',
'4. 减少bundle大小',
'5. 与Client Components组合使用'
]
},
Q2: {
question: 'Server Components和SSR的区别?',
answer: `
SSR:
- 服务端生成HTML
- 客户端需要hydration
- bundle包含所有组件
RSC:
- 服务端执行组件
- 序列化组件树
- 只有Client Component需要hydration
- bundle只包含Client Component
可以组合使用获得最佳效果
`
},
Q3: {
question: 'Server Components有哪些限制?',
answer: [
'1. 不能使用useState/useEffect',
'2. 不能使用浏览器API',
'3. 不能有事件处理器',
'4. props必须可序列化',
'5. 不能使用Context Provider'
]
},
Q4: {
question: '如何在Server和Client Component间传递数据?',
answer: `
Server -> Client:
- 通过props传递
- props必须可序列化
- 可以传递Promise(用use读取)
Client -> Server:
- 通过Server Actions
- 表单提交
- API调用
`
},
Q5: {
question: 'Server Components的性能优势?',
answer: [
'1. 减少客户端bundle大小',
'2. 零JavaScript(静态内容)',
'3. 更快的数据获取',
'4. 自动代码分割',
'5. 流式渲染',
'6. 选择性hydration'
]
}
};11. 总结
Server Components的核心要点:
- 定义: 只在服务端运行的组件
- 优势: 零bundle、直接后端访问
- 限制: 不能用Hooks、不能交互
- 组合: 与Client Component配合
- 数据: async/await直接获取
- 性能: 流式渲染、选择性hydration
- 最佳实践: 默认Server,按需Client
Server Components是React未来的重要方向。