Appearance
第3章:React Server 基础概念详解
3.1 组件分类与边界划分
组件类型分类图
Server Components 详解
定义特征:
- 只在服务器端执行
- 可以直接访问后端资源(数据库、文件系统、API)
- 不包含在客户端 Bundle 中
- 不能使用浏览器专有 API
- 不能使用状态(useState)或效果(useEffect)
javascript
// ✅ 正确的 Server Component 示例
async function ProductList({ category }) {
// 直接访问数据库
const products = await db.products.findByCategory(category);
// 直接访问文件系统
const config = await fs.readFile('./config.json', 'utf8');
// 直接调用内部 API
const analytics = await internalAPI.getAnalytics(category);
return (
<div className="product-list">
<h2>{category} 产品列表</h2>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
analytics={analytics[product.id]}
/>
))}
</div>
);
}
// ✅ 另一个 Server Component 示例
async function BlogPost({ slug }) {
// 服务器端数据获取
const [post, author, comments] = await Promise.all([
cms.posts.findBySlug(slug),
cms.authors.findById(post.authorId),
cms.comments.findByPostId(post.id)
]);
return (
<article>
<header>
<h1>{post.title}</h1>
<AuthorInfo author={author} />
<PublishDate date={post.publishedAt} />
</header>
<main>
<PostContent content={post.content} />
</main>
<footer>
<CommentList comments={comments} />
{/* 客户端组件用于交互 */}
<CommentForm postId={post.id} />
</footer>
</article>
);
}
Server Components 的限制:
javascript
// ❌ Server Component 中不能使用的功能
// 1. 不能使用状态
function ServerComponent() {
const [count, setCount] = useState(0); // ❌ 错误
return <div>{count}</div>;
}
// 2. 不能使用效果
function ServerComponent() {
useEffect(() => { // ❌ 错误
console.log('mounted');
}, []);
return <div>Hello</div>;
}
// 3. 不能使用浏览器 API
function ServerComponent() {
const handleClick = () => {
window.alert('Hello'); // ❌ 错误:window 不存在
};
return <button onClick={handleClick}>Click</button>;
}
// 4. 不能使用事件处理器
function ServerComponent() {
return (
<button onClick={() => console.log('clicked')}> {/* ❌ 错误 */}
Click me
</button>
);
}
Client Components 详解
定义特征:
- 在客户端执行(也可能在服务器端预渲染)
- 包含在客户端 Bundle 中
- 可以使用所有 React 功能(状态、效果、事件处理)
- 可以访问浏览器 API
- 需要
'use client'
指令声明
javascript
// ✅ 正确的 Client Component 示例
'use client';
import { useState, useEffect } from 'react';
function InteractiveCounter({ initialValue = 0 }) {
const [count, setCount] = useState(initialValue);
const [isClient, setIsClient] = useState(false);
// 客户端效果
useEffect(() => {
setIsClient(true);
// 访问浏览器 API
const savedCount = localStorage.getItem('counter');
if (savedCount) {
setCount(parseInt(savedCount, 10));
}
}, []);
// 事件处理
const handleIncrement = () => {
const newCount = count + 1;
setCount(newCount);
// 保存到本地存储
localStorage.setItem('counter', newCount.toString());
};
const handleDecrement = () => {
const newCount = count - 1;
setCount(newCount);
localStorage.setItem('counter', newCount.toString());
};
// 防止 hydration 不匹配
if (!isClient) {
return <div>计数器: {initialValue}</div>;
}
return (
<div className="counter">
<h3>交互式计数器</h3>
<div className="count-display">{count}</div>
<div className="controls">
<button onClick={handleDecrement}>-</button>
<button onClick={handleIncrement}>+</button>
</div>
</div>
);
}
// ✅ 复杂的 Client Component 示例
'use client';
import { useState, useEffect, useCallback } from 'react';
function ChatWidget({ userId, initialMessages = [] }) {
const [messages, setMessages] = useState(initialMessages);
const [newMessage, setNewMessage] = useState('');
const [isConnected, setIsConnected] = useState(false);
const [ws, setWs] = useState(null);
// WebSocket 连接
useEffect(() => {
const websocket = new WebSocket(`ws://localhost:3001/chat/${userId}`);
websocket.onopen = () => {
setIsConnected(true);
setWs(websocket);
};
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
websocket.onclose = () => {
setIsConnected(false);
setWs(null);
};
return () => {
websocket.close();
};
}, [userId]);
// 发送消息
const sendMessage = useCallback(() => {
if (newMessage.trim() && ws && isConnected) {
const message = {
id: Date.now(),
userId,
content: newMessage,
timestamp: new Date().toISOString()
};
ws.send(JSON.stringify(message));
setMessages(prev => [...prev, message]);
setNewMessage('');
}
}, [newMessage, ws, isConnected, userId]);
// 键盘事件处理
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
return (
<div className="chat-widget">
<div className="chat-header">
<h4>聊天室</h4>
<div className={`status ${isConnected ? 'connected' : 'disconnected'}`}>
{isConnected ? '已连接' : '未连接'}
</div>
</div>
<div className="messages">
{messages.map(message => (
<div key={message.id} className="message">
<span className="timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
<span className="content">{message.content}</span>
</div>
))}
</div>
<div className="input-area">
<textarea
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="输入消息..."
disabled={!isConnected}
/>
<button
onClick={sendMessage}
disabled={!newMessage.trim() || !isConnected}
>
发送
</button>
</div>
</div>
);
}
Shared Components
定义特征:
- 可以在服务器和客户端都使用
- 通常是纯函数组件
- 不依赖特定环境的 API
- 主要用于工具函数和类型定义
javascript
// ✅ Shared Component 示例
// 1. 纯函数组件
function formatDate(date) {
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(date));
}
function DateDisplay({ date, className }) {
return (
<time className={className} dateTime={date}>
{formatDate(date)}
</time>
);
}
// 2. 工具组件
function Avatar({ src, alt, size = 'medium' }) {
const sizeClasses = {
small: 'w-8 h-8',
medium: 'w-12 h-12',
large: 'w-16 h-16'
};
return (
<img
src={src}
alt={alt}
className={`rounded-full ${sizeClasses[size]}`}
/>
);
}
// 3. 布局组件
function Card({ children, className = '', ...props }) {
return (
<div
className={`bg-white rounded-lg shadow-md p-6 ${className}`}
{...props}
>
{children}
</div>
);
}
// 4. 类型定义(TypeScript)
interface User {
id: string;
name: string;
email: string;
avatar?: string;
}
interface Post {
id: string;
title: string;
content: string;
authorId: string;
publishedAt: string;
tags: string[];
}
3.2 渲染机制深入解析
RSC 渲染流程图
组件树渲染过程
数据流向详解
javascript
// 数据流示例:博客应用
// 1. 根 Server Component
async function BlogApp({ slug }) {
// 服务器端数据获取
const post = await db.posts.findBySlug(slug);
const author = await db.users.findById(post.authorId);
return (
<div className="blog-app">
{/* Server Component - 静态内容 */}
<BlogHeader post={post} author={author} />
{/* Server Component - 文章内容 */}
<BlogContent content={post.content} />
{/* Client Component - 交互功能 */}
<BlogInteractions
postId={post.id}
initialLikes={post.likes}
initialComments={post.commentCount}
/>
</div>
);
}
// 2. Server Component - 博客头部
async function BlogHeader({ post, author }) {
// 可以进行额外的数据获取
const authorStats = await db.users.getStats(author.id);
return (
<header className="blog-header">
<h1>{post.title}</h1>
<div className="author-info">
<Avatar src={author.avatar} alt={author.name} />
<div>
<h3>{author.name}</h3>
<p>{authorStats.totalPosts} 篇文章</p>
</div>
</div>
<time>{formatDate(post.publishedAt)}</time>
</header>
);
}
// 3. Server Component - 文章内容
function BlogContent({ content }) {
// 纯渲染逻辑,无需数据获取
return (
<main className="blog-content">
<div dangerouslySetInnerHTML={{ __html: content }} />
</main>
);
}
// 4. Client Component - 交互功能
'use client';
function BlogInteractions({ postId, initialLikes, initialComments }) {
const [likes, setLikes] = useState(initialLikes);
const [hasLiked, setHasLiked] = useState(false);
const [showComments, setShowComments] = useState(false);
const handleLike = async () => {
if (!hasLiked) {
setLikes(prev => prev + 1);
setHasLiked(true);
// API 调用
await fetch(`/api/posts/${postId}/like`, {
method: 'POST'
});
}
};
return (
<div className="blog-interactions">
<button
onClick={handleLike}
className={`like-button ${hasLiked ? 'liked' : ''}`}
>
❤️ {likes}
</button>
<button
onClick={() => setShowComments(!showComments)}
className="comments-button"
>
💬 {initialComments} 评论
</button>
{showComments && (
<CommentSection postId={postId} />
)}
</div>
);
}
Flight Protocol 数据格式
javascript
// Flight Protocol 序列化示例
// 原始组件树
const componentTree = (
<BlogApp slug="react-server-components">
<BlogHeader post={post} author={author} />
<BlogContent content={post.content} />
<BlogInteractions
postId={post.id}
initialLikes={post.likes}
initialComments={post.commentCount}
/>
</BlogApp>
);
// 序列化后的 Flight 数据
const flightPayload = {
// 组件树结构
"0": {
type: "div",
props: {
className: "blog-app",
children: ["1", "2", "3"]
}
},
// BlogHeader 渲染结果
"1": {
type: "header",
props: {
className: "blog-header",
children: [
{
type: "h1",
props: { children: "React Server Components 深入解析" }
},
{
type: "div",
props: {
className: "author-info",
children: [/* 作者信息 */]
}
}
]
}
},
// BlogContent 渲染结果
"2": {
type: "main",
props: {
className: "blog-content",
children: {
type: "div",
props: {
dangerouslySetInnerHTML: {
__html: "<p>文章内容...</p>"
}
}
}
}
},
// BlogInteractions 客户端组件引用
"3": {
$$typeof: Symbol.for("react.element"),
type: {
$$typeof: Symbol.for("react.client.reference"),
name: "BlogInteractions",
id: "./components/BlogInteractions.js"
},
props: {
postId: "123",
initialLikes: 42,
initialComments: 8
}
},
// 客户端组件映射
clientReferences: {
"./components/BlogInteractions.js": {
id: "chunk-interactions",
chunks: ["chunk-interactions.js"],
name: "BlogInteractions"
}
}
};
3.3 数据获取模式
数据获取策略对比
并行数据获取示例
javascript
// ✅ RSC 中的高效数据获取
// 1. 并行数据获取
async function UserDashboard({ userId }) {
// 并行获取多个数据源
const [user, posts, notifications, analytics] = await Promise.all([
db.users.findById(userId),
db.posts.findByUserId(userId),
db.notifications.findByUserId(userId),
analytics.getUserStats(userId)
]);
return (
<div className="dashboard">
<UserProfile user={user} analytics={analytics} />
<PostList posts={posts} />
<NotificationCenter notifications={notifications} />
</div>
);
}
// 2. 嵌套数据获取
async function PostList({ posts }) {
// 为每个帖子获取额外数据
const postsWithStats = await Promise.all(
posts.map(async (post) => {
const [likes, comments] = await Promise.all([
db.likes.countByPostId(post.id),
db.comments.countByPostId(post.id)
]);
return {
...post,
likesCount: likes,
commentsCount: comments
};
})
);
return (
<div className="post-list">
{postsWithStats.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
// 3. 条件数据获取
async function PostCard({ post }) {
// 根据条件获取不同数据
let additionalData = null;
if (post.type === 'premium') {
additionalData = await db.premium.getPostData(post.id);
} else if (post.type === 'sponsored') {
additionalData = await db.sponsors.getPostData(post.id);
}
return (
<article className="post-card">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
{additionalData && (
<div className="additional-info">
{/* 渲染额外数据 */}
</div>
)}
<PostInteractions postId={post.id} />
</article>
);
}
数据缓存策略
javascript
// 数据缓存实现示例
// 1. 内存缓存
const cache = new Map();
async function getCachedUserData(userId) {
const cacheKey = `user:${userId}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const userData = await db.users.findById(userId);
cache.set(cacheKey, userData);
// 设置过期时间
setTimeout(() => {
cache.delete(cacheKey);
}, 5 * 60 * 1000); // 5分钟
return userData;
}
// 2. Redis 缓存
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function getCachedPostData(postId) {
const cacheKey = `post:${postId}`;
// 尝试从 Redis 获取
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 从数据库获取
const postData = await db.posts.findById(postId);
// 存储到 Redis(1小时过期)
await redis.setex(cacheKey, 3600, JSON.stringify(postData));
return postData;
}
// 3. 使用缓存的 Server Component
async function CachedUserProfile({ userId }) {
const user = await getCachedUserData(userId);
const posts = await getCachedPostData(userId);
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>{user.bio}</p>
<div className="user-stats">
<span>{posts.length} 篇文章</span>
<span>{user.followers} 关注者</span>
</div>
</div>
);
}
3.4 错误处理与边界
错误边界层次图
Server Component 错误处理
javascript
// Server Component 错误处理示例
// 1. 基础错误处理
async function UserProfile({ userId }) {
try {
const user = await db.users.findById(userId);
if (!user) {
return (
<div className="error-state">
<h3>用户不存在</h3>
<p>请检查用户 ID 是否正确</p>
</div>
);
}
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
} catch (error) {
console.error('获取用户数据失败:', error);
return (
<div className="error-state">
<h3>加载失败</h3>
<p>无法加载用户信息,请稍后重试</p>
</div>
);
}
}
// 2. 带降级的错误处理
async function PostList({ userId }) {
try {
const posts = await db.posts.findByUserId(userId);
return <PostGrid posts={posts} />;
} catch (error) {
console.error('获取帖子失败:', error);
// 尝试获取缓存数据
try {
const cachedPosts = await cache.get(`posts:${userId}`);
if (cachedPosts) {
return (
<div>
<div className="warning-banner">
显示的是缓存数据,可能不是最新的
</div>
<PostGrid posts={cachedPosts} />
</div>
);
}
} catch (cacheError) {
console.error('缓存获取失败:', cacheError);
}
// 最终降级 UI
return (
<div className="error-state">
<h3>暂时无法加载帖子</h3>
<p>请稍后刷新页面重试</p>
</div>
);
}
}
// 3. 错误边界组件
'use client';
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('错误边界捕获到错误:', error, errorInfo);
// 错误上报
this.reportError(error, errorInfo);
}
reportError = (error, errorInfo) => {
// 发送错误报告到监控服务
fetch('/api/error-report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.toString(),
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString()
})
}).catch(reportError => {
console.error('错误上报失败:', reportError);
});
};
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>出现了一些问题</h2>
<p>我们已经记录了这个错误,请稍后重试</p>
<button
onClick={() => this.setState({ hasError: false, error: null })}
>
重试
</button>
</div>
);
}
return this.props.children;
}
}
// 使用错误边界
function App() {
return (
<ErrorBoundary>
<Header />
<main>
<ErrorBoundary>
<UserDashboard userId="123" />
</ErrorBoundary>
<ErrorBoundary>
<PostList userId="123" />
</ErrorBoundary>
</main>
<Footer />
</ErrorBoundary>
);
}
3.5 性能优化策略
性能优化层次图
具体优化实现
javascript
// 1. 数据库查询优化
// ❌ 低效的 N+1 查询
async function PostListBad({ userId }) {
const posts = await db.posts.findByUserId(userId);
const postsWithAuthors = [];
for (const post of posts) {
const author = await db.users.findById(post.authorId); // N+1 问题
postsWithAuthors.push({ ...post, author });
}
return <PostGrid posts={postsWithAuthors} />;
}
// ✅ 优化的批量查询
async function PostListGood({ userId }) {
const posts = await db.posts.findByUserId(userId);
// 批量获取作者信息
const authorIds = [...new Set(posts.map(post => post.authorId))];
const authors = await db.users.findByIds(authorIds);
const authorMap = new Map(authors.map(author => [author.id, author]));
const postsWithAuthors = posts.map(post => ({
...post,
author: authorMap.get(post.authorId)
}));
return <PostGrid posts={postsWithAuthors} />;
}
// 2. 智能缓存实现
class SmartCache {
constructor() {
this.cache = new Map();
this.timestamps = new Map();
this.ttl = 5 * 60 * 1000; // 5分钟
}
set(key, value, customTTL) {
this.cache.set(key, value);
this.timestamps.set(key, Date.now() + (customTTL || this.ttl));
}
get(key) {
if (!this.cache.has(key)) {
return null;
}
const expiry = this.timestamps.get(key);
if (Date.now() > expiry) {
this.cache.delete(key);
this.timestamps.delete(key);
return null;
}
return this.cache.get(key);
}
invalidate(pattern) {
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
this.timestamps.delete(key);
}
}
}
}
const smartCache = new SmartCache();
// 3. 流式渲染优化
async function OptimizedPage({ params }) {
// 立即可用的数据
const basicData = await getBasicData(params.id);
return (
<div>
{/* 立即渲染的内容 */}
<PageHeader data={basicData} />
{/* 延迟加载的内容 */}
<Suspense fallback={<SkeletonLoader />}>
<HeavyContent id={params.id} />
</Suspense>
<Suspense fallback={<SkeletonLoader />}>
<AnotherHeavyContent id={params.id} />
</Suspense>
</div>
);
}
// 4. 预加载策略
function PreloadingExample() {
return (
<div>
{/* 预加载关键资源 */}
<link rel="preload" href="/api/critical-data" as="fetch" />
<link rel="prefetch" href="/api/secondary-data" as="fetch" />
{/* 预加载组件 */}
<link
rel="modulepreload"
href="/chunks/important-component.js"
/>
</div>
);
}
小结
本章深入探讨了 React Server 的核心概念:
- 组件分类:Server Components、Client Components 和 Shared Components 的特点和使用场景
- 渲染机制:RSC 的渲染流程和 Flight Protocol 的工作原理
- 数据获取:高效的数据获取模式和缓存策略
- 错误处理:完善的错误边界和降级机制
- 性能优化:从服务器到客户端的全链路优化策略
下一章我们将深入研究传统 SSR 的实现细节,为理解 RSC 的优势打下基础。
