React Server Components(RSC)是React 18引入的服务端组件架构,Next.js App Router将其落地为可用的开发模式。RSC将组件渲染拆分到服务端和客户端:Server Components在服务端执行可直接访问数据库和文件系统,Client Components在浏览器执行处理交互逻辑。理解RSC的数据获取机制、流式渲染和组件边界划分,是前端工程化中构建高性能React应用的关键,Web性能优化因此进入新阶段。
Server Components与Client Components的边界划分
App Router中默认所有组件都是Server Components。需要浏览器交互(useState、onClick、useEffect等)的组件通过”use client”指令声明为Client Components。边界划分原则:尽量将组件树保持为Server Components,只在需要交互的叶子节点切换为Client Components。
// app/products/page.tsx (Server Component)
import { db } from '@/lib/db'
import ProductFilter from './ProductFilter'
export default async function ProductsPage({
searchParams,
}: {
searchParams: { category?: string }
}) {
const products = await db.query(
'SELECT * FROM products WHERE category = $1',
[searchParams.category || 'all']
)
return (
<div>
<ProductFilter initialCategory={searchParams.category} />
<ProductList products={products} />
</div>
)
}
// app/products/ProductFilter.tsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function ProductFilter({ initialCategory }: {
initialCategory?: string
}) {
const router = useRouter()
const [category, setCategory] = useState(initialCategory || 'all')
const handleChange = (value: string) => {
setCategory(value)
router.push(`/products?category=${value}`)
}
return (
<select value={category} onChange={(e) => handleChange(e.target.value)}>
<option value="all">全部</option>
<option value="electronics">电子产品</option>
</select>
)
}
Server Components不能使用useState、useEffect、浏览器API。Client Components不能直接使用async/await获取数据,需通过props传入或使用SWR/React Query。
Server Components中的数据获取模式
RSC中数据获取在服务端完成,组件可直接使用async/await。Next.js 14支持在Server Components中直接访问数据库、调用内部服务,省去API中间层。
// app/dashboard/page.tsx
import { db } from '@/lib/db'
import { Suspense } from 'react'
export default async function Dashboard() {
const user = await getUser()
// Promise.all并行请求无依赖数据
const [orders, revenue, cacheData] = await Promise.all([
db.query('SELECT COUNT(*) FROM orders WHERE user_id = $1', [user.id]),
db.query('SELECT SUM(amount) FROM payments WHERE user_id = $1', [user.id]),
redis.get(`user:${user.id}:cache`),
])
return (
<main>
<Suspense fallback={<MetricsCard.Skeleton />}>
<MetricsCard
orderCount={orders.rows[0].count}
revenue={revenue.rows[0].sum}
/>
</Suspense>
</main>
)
}
Suspense包裹的组件会流式渲染:先返回已就绪的HTML,异步数据准备好后再通过流式响应推送给浏览器。用户看到页面渐进式填充而非白屏等待。
嵌套Suspense实现渐进式流式渲染
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
return (
<div>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<MainSkeleton />}>
{children}
</Suspense>
</div>
)
}
每个Suspense边界独立流式渲染,Header先就绪先返回,Sidebar和主内容区域在数据加载后陆续填充。这种模式使首屏时间(FCP)大幅缩短——用户不需要等待所有数据加载完毕才看到页面内容。
数据请求缓存与重新验证策略
Next.js App Router的fetch自动缓存机制替代了getStaticProps和getServerSideProps。通过cache和next.revalidate参数控制缓存行为。
// 模式1: 静态生成(构建时获取,永久缓存)
async function getCategories() {
const res = await fetch('https://api.example.com/categories', {
cache: 'force-cache'
})
return res.json()
}
// 模式2: ISR增量静态再生成
async function getArticle(slug: string) {
const res = await fetch(`https://api.example.com/articles/${slug}`, {
next: { revalidate: 3600 }
})
return res.json()
}
// 模式3: 动态渲染(每次请求都获取)
async function getUserStats(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}/stats`, {
cache: 'no-store'
})
return res.json()
}
// 模式4: 按需重新验证
import { revalidateTag, revalidatePath } from 'next/cache'
export async function POST(request: Request) {
const { slug } = await request.json()
revalidatePath(`/blog/${slug}`)
revalidateTag('articles')
return Response.json({ revalidated: true })
}
Server Actions实现表单提交与数据变更
Server Actions让Client Components直接调用服务端函数处理表单提交和数据变更,无需编写API路由。Server Actions在服务端执行,自动处理CSRF防护。
// app/todos/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'
export async function createTodo(formData: FormData) {
const title = formData.get('title') as string
await db.query('INSERT INTO todos (title) VALUES ($1)', [title])
revalidatePath('/todos')
}
// app/todos/TodoForm.tsx
'use client'
import { useFormStatus, useActionState } from 'react'
import { createTodo } from './actions'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '添加'}
</button>
)
}
export default function TodoForm() {
const [state, formAction] = useActionState(createTodo, { error: null })
return (
<form action={formAction}>
<input name="title" type="text" />
{state?.error && <p className="error">{state.error}</p>}
<SubmitButton />
</form>
)
}
useFormStatus自动跟踪表单提交状态,useActionState处理返回结果和错误。表单提交后revalidatePath自动刷新页面数据,无需手动refetch。
RSC性能优化实践要点
1. 减少Client Components的props序列化体积。Server Components传给Client Components的props会被序列化为JSON,避免传递大对象或Date/Map等不可序列化类型。只传必要字段而非整个ORM对象。
2. 将获取数据的代码放在组件树的尽可能深处,利用嵌套Suspense实现局部流式渲染。避免在根layout中获取所有数据导致整个页面阻塞。
3. 使用next/dynamic延迟加载Client Components,减少首屏JavaScript体积:
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <div style={{ height: 400 }}>加载中...</div>,
ssr: false
})
4. 使用optimizePackageImports优化大型组件库(如lucide-react、antd)的按需导入,减少打包体积。在next.config.js中配置experimental.optimizePackageImports即可。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-shu-ju-huo-qu-yu-liu-shi-xuan-ran-ji/