React Server Components 深度解析
React Server Components 深度解析
React Server Components (RSC) 是 React 18 引入的革命性特性,它允许组件在服务器端渲染,并将渲染结果流式传输到客户端。RSC 代表了 React 架构的重大演进,它重新定义了前端和后端的边界,为构建高性能 Web 应用提供了新的范式。
核心概念详解
服务器组件 (Server Components):
服务器组件是在服务器端执行的 React 组件,其代码不会发送到客户端。服务器组件可以直接访问数据库、文件系统、内部 API 等服务器资源,无需通过 API 层。服务器组件的渲染结果是序列化的 JSX,流式传输到客户端后由 React 恢复。服务器组件不支持状态(useState)和副作用(useEffect),因为它们不会在客户端执行。服务器组件的优势包括:零客户端 JavaScript 体积、直接访问后端资源、更快的首屏渲染、更好的 SEO。
客户端组件 (Client Components):
客户端组件是传统的 React 组件,在浏览器中执行。客户端组件支持所有 React 特性:状态管理、副作用、事件处理、浏览器 API 等。在 Next.js App Router 中,需要使用 'use client' 指令标记客户端组件。客户端组件会增加 JavaScript 包大小,但提供了丰富的交互能力。合理划分服务器组件和客户端组件是 RSC 架构的关键。
组件边界与组合:
服务器组件和客户端组件可以组合使用,形成组件树。服务器组件可以导入和渲染客户端组件,但客户端组件不能导入服务器组件。服务器组件可以通过 props 向客户端组件传递数据,包括序列化的 JSON 数据和 React 元素(作为 children)。理解组件边界对于正确使用 RSC 至关重要。
代码示例
// 服务器组件示例 (默认)
// app/users/page.tsx
async function UsersPage() {
// 直接访问数据库,无需 API
const users = await db.users.findMany({
select: { id: true, name: true, email: true }
});
return (
<div>
<h1>Users List</h1>
<UserList users={users} />
</div>
);
}
// 服务器组件中的数据获取
async function UserProfile({ userId }) {
// 并行获取多个数据源
const [user, posts, followers] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchFollowers(userId)
]);
return (
<div>
<UserHeader user={user} />
<Suspense fallback={<PostsSkeleton />}>
<PostsList posts={posts} />
</Suspense>
<FollowersList followers={followers} />
</div>
);
}
// 客户端组件
'use client';
import { useState } from 'react';
function LikeButton({ postId, initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const [isLiked, setIsLiked] = useState(false);
const handleLike = async () => {
const newLikedState = !isLiked;
setIsLiked(newLikedState);
setLikes(prev => newLikedState ? prev + 1 : prev - 1);
// 调用 API 更新服务器状态
await fetch(`/api/posts/${postId}/like`, {
method: newLikedState ? 'POST' : 'DELETE'
});
};
return (
<button onClick={handleLike}>
{isLiked ? '❤️' : '🤍'} {likes}
</button>
);
}
// 服务器组件传递 props 给客户端组件
async function PostCard({ postId }) {
const post = await fetchPost(postId);
return (
<article>
<h2>{post.title}</h2>
<p>{post.content}</p>
{/* 传递序列化数据给客户端组件 */}
<LikeButton postId={post.id} initialLikes={post.likes} />
</article>
);
}
// 服务器组件传递 children 给客户端组件
// components/ClientWrapper.tsx
'use client';
import { useState } from 'react';
export function ExpandableSection({ children, title }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="expandable-section">
<button onClick={() => setIsExpanded(!isExpanded)}>
{title} {isExpanded ? '−' : '+'}
</button>
{isExpanded && <div className="content">{children}</div>}
</div>
);
}
// app/page.tsx (服务器组件)
import { ExpandableSection } from '@/components/ClientWrapper';
async function Page() {
const data = await fetchData();
return (
<div>
{/* 服务器组件内容作为 children 传递给客户端组件 */}
<ExpandableSection title="Details">
<ServerRenderedContent data={data} />
</ExpandableSection>
</div>
);
}
// 流式渲染与 Suspense
async function StreamingPage() {
return (
<div>
<h1>Dashboard</h1>
{/* 快速加载的内容 */}
<QuickStats />
{/* 流式加载的内容 */}
<Suspense fallback={<ChartSkeleton />}>
<SlowChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable />
</Suspense>
</div>
);
}
// 服务器组件中的错误处理
async function SafeDataFetch({ userId }) {
try {
const user = await fetchUser(userId);
return <UserProfile user={user} />;
} catch (error) {
// 错误会冒泡到最近的 Error Boundary
throw new Error('Failed to load user');
}
}
// 错误边界组件
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
// 服务器操作 (Server Actions)
async function updateProfile(formData) {
'use server';
const userId = formData.get('userId');
const name = formData.get('name');
await db.users.update({
where: { id: userId },
data: { name }
});
revalidatePath('/profile');
}
// 在服务器组件中使用 Server Action
async function ProfileForm({ user }) {
return (
<form action={updateProfile}>
<input type="hidden" name="userId" value={user.id} />
<input type="text" name="name" defaultValue={user.name} />
<button type="submit">Update</button>
</form>
);
}
// 混合使用服务器和客户端组件的最佳实践
// app/layout.tsx
import { Navbar } from '@/components/Navbar';
import { Sidebar } from '@/components/Sidebar';
export default function RootLayout({ children }) {
return (
<html>
<body>
{/* 静态布局 - 服务器组件 */}
<Navbar />
<div className="flex">
<Sidebar />
<main>{children}</main>
</div>
</body>
</html>
);
}
// components/Navbar.tsx
'use client';
import { useState } from 'react';
export function Navbar() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<nav>
<button onClick={() => setIsMenuOpen(!isMenuOpen)}>
Menu
</button>
{isMenuOpen && <MobileMenu />}
</nav>
);
}使用场景划分
服务器组件适用场景:
客户端组件适用场景: