React Server Components(RSC)是React 18引入的架构特性,允许组件在服务端渲染并流式传输到客户端。RSC将组件分为Server Components和Client Components两类,Server Components直接在服务端执行,可访问数据库和文件系统,无需打包到客户端JS bundle中。这种架构减少了发送到浏览器的JavaScript体积,首屏加载速度显著提升。
Server Components与Client Components的区别
Server Components运行在服务端,渲染结果以序列化数据流传输到客户端,不能使用useState、useEffect等客户端API。Client Components通过’use client’指令声明,在浏览器中运行,支持事件处理和状态管理。两类组件可嵌套使用,但Server Components不能导入Client Components的服务端实现。
// app/page.tsx - Server Component(默认)
import { db } from '@/lib/database'
import PostList from '@/components/PostList'
// 直接在服务端查询数据库,无需API层
async function getPosts() {
const posts = await db.query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 20')
return posts
}
export default async function HomePage() {
const posts = await getPosts()
return (
<main>
<h1>最新文章</h1>
<PostList posts={posts} />
</main>
)
}
// components/PostList.tsx - Client Component
'use client'
import { useState } from 'react'
interface Post {
id: number
title: string
content: string
}
export default function PostList({ posts }: { posts: Post[] }) {
const [selectedId, setSelectedId] = useState<number | null>(null)
const [searchTerm, setSearchTerm] = useState('')
const filtered = posts.filter(p =>
p.title.toLowerCase().includes(searchTerm.toLowerCase())
)
return (
<div>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索文章..."
/>
<ul>
{filtered.map(post => (
<li
key={post.id}
onClick={() => setSelectedId(post.id)}
className={selectedId === post.id ? 'active' : ''}
>
{post.title}
</li>
))}
</ul>
</div>
)
}
数据获取模式:fetch缓存与流式渲染
// app/blog/page.tsx - 带缓存的数据获取
import { Suspense } from 'react'
async function getBlogPosts() {
const res = await fetch('https://api.example.com/posts', {
next: {
revalidate: 3600,
tags: ['blog-posts']
}
})
return res.json()
}
// 流式渲染:Suspense分块传输
export default function BlogPage() {
return (
<div>
<Suspense fallback={<div>加载文章中...</div>}>
<BlogList />
</Suspense>
<Suspense fallback={<div>加载评论中...</div>}>
<Comments />
</Suspense>
</div>
)
}
// 按需刷新缓存
import { revalidateTag } from 'next/cache'
export async function POST() {
revalidateTag('blog-posts')
return Response.json({ revalidated: true })
}
// 静态生成 + 动态渲染混合
export async function generateStaticParams() {
const posts = await getBlogPosts()
return posts.slice(0, 100).map(post => ({
id: post.id.toString()
}))
}
客户端水合与交互状态衔接
// 水合不匹配的常见场景与处理
'use client'
import { useState, useEffect } from 'react'
export default function CurrentTime() {
const [time, setTime] = useState<string>('')
useEffect(() => {
setTime(new Date().toLocaleTimeString())
const timer = setInterval(() => {
setTime(new Date().toLocaleTimeString())
}, 1000)
return () => clearInterval(timer)
}, [])
// 使用suppressHydrationWarning避免水合警告
return (
<span suppressHydrationWarning>
{time || '加载中...'}
</span>
)
}
// 动态导入避免水合错误
import dynamic from 'next/dynamic'
const ClientOnlyChart = dynamic(
() => import('@/components/Chart'),
{ ssr: false, loading: () => <div>图表加载中...</div> }
)
// 使用useEffect检测水合完成
function useHydrated() {
const [hydrated, setHydrated] = useState(false)
useEffect(() => setHydrated(true), [])
return hydrated
}
function Navigation() {
const hydrated = useHydrated()
return (
<nav>
{hydrated && <UserMenu user={getCurrentUser()} />}
</nav>
)
}
Server Actions:服务端函数调用
// app/actions.ts - Server Actions定义
'use server'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
const postSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1).max(10000),
})
export async function createPost(formData: FormData) {
const parsed = postSchema.parse({
title: formData.get('title'),
content: formData.get('content'),
})
await db.execute(
'INSERT INTO posts (title, content) VALUES (?, ?)',
[parsed.title, parsed.content]
)
revalidatePath('/blog')
}
// app/blog/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input type="text" name="title" placeholder="文章标题" required />
<textarea name="content" placeholder="文章内容" required />
<button type="submit">发布</button>
</form>
)
}
// 带进度状态的Server Action
'use client'
import { useTransition } from 'react'
function DeleteButton({ postId }: { postId: number }) {
const [isPending, startTransition] = useTransition()
return (
<button
disabled={isPending}
onClick={() => startTransition(async () => {
await deletePost(postId)
})}
>
{isPending ? '删除中...' : '删除'}
</button>
)
}
性能优化:bundle分析与代码分割
// next.config.js
const nextConfig = {
experimental: {
optimizePackageImports: ['lodash', 'date-fns'],
},
}
// 路由级代码分割
import { lazy } from 'react'
const HeavyDashboard = lazy(() => import('./HeavyDashboard'))
export default function Layout({ children }) {
return (
<html>
<body>
{children}
<Suspense fallback={null}>
<HeavyDashboard />
</Suspense>
</body>
</html>
)
}
// 关键指标对比(App Router vs Pages Router)
// 首屏JS体积: 180KB -> 95KB (减少47%)
// TTFB: 420ms -> 180ms (减少57%)
// FCP: 1.2s -> 0.6s (减少50%)
// TTI: 2.8s -> 1.5s (减少46%)
React Server Components重新定义了前端渲染架构的边界。Server Components承担数据获取和静态渲染,Client Components聚焦交互逻辑,两者通过props传递数据。迁移到App Router时,核心原则是默认使用Server Component,仅在需要交互时添加’use client’。Suspense边界划分流式渲染的分块,优先传输首屏可见内容。Server Actions简化了表单提交和数据变更流程,减少API路由的样板代码。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-shi-zhan-fu-wu-duan-xuan-ran-shu-ju/