React Server Components(RSC)是React 18引入的组件渲染模型,将组件分为Server Component和Client Component两种类型。Server Component在服务端执行,可直接访问数据库和文件系统,零JavaScript运行时开销。结合Next.js App Router的流式渲染能力,RSC实现了首屏HTML快速输出与渐进式数据加载的平衡。
Server Components与Client Components的渲染边界
App Router中默认所有组件都是Server Component。文件顶部添加”use client”指令的组件才是Client Component。两种组件的核心差异:
– Server Component:在Node.js服务端执行,可使用async/await直接读取数据库,不能使用useState、useEffect等客户端Hook,不能绑定事件监听器
– Client Component:在浏览器端执行(SSR时也会在服务端预渲染),可使用React Hook和浏览器API,不能直接访问后端资源
– 组合规则:Server Component可以导入Client Component,但Client Component不能导入Server Component(可以通过children props传递)
// app/products/page.tsx (Server Component)
import { db } from '@/lib/db'
import ProductList from './ProductList'
// 默认是Server Component,async组件支持await
export default async function ProductsPage() {
// 直接查询数据库,无需API层
const products = await db.product.findMany({
where: { status: 'active' },
include: { category: true }
})
return (
<div>
<h1>商品列表</h1>
{/* Server Component可以传递数据给Client Component */}
<ProductList products={products} />
</div>
)
}
// app/products/ProductList.tsx
'use client'
import { useState } from 'react'
export default function ProductList({ products }) {
const [filter, setFilter] = useState('')
const filtered = products.filter(p =>
p.name.includes(filter)
)
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="搜索商品..."
/>
{filtered.map(p => (
<div key={p.id}>{p.name} - {p.category.name}</div>
))}
</div>
)
}
流式渲染与Suspense边界配置
流式渲染(Streaming)允许服务端将页面分块输出到浏览器,先渲染静态内容,慢速数据加载完成后再流式推送对应内容。Suspense组件定义了流式渲染的边界,每个Suspense包裹的异步组件独立加载,不阻塞页面其他区域。
// app/dashboard/layout.tsx
import { Suspense } from 'react'
import Stats from './Stats'
import RecentOrders from './RecentOrders'
import ActivityChart from './ActivityChart'
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
{/* 立即渲染的静态部分 */}
<header>数据看板</header>
{/* 快速加载的数据 */}
<Suspense fallback={<Skeleton />}>
<Stats />
</Suspense>
{/* 慢速加载的数据,不阻塞其他区域 */}
<Suspense fallback={<ChartSkeleton />}>
<ActivityChart />
</Suspense>
{/* 最慢的数据放最后 */}
<Suspense fallback={<OrderSkeleton />}>
<RecentOrders />
</Suspense>
{children}
</div>
)
}
// app/dashboard/ActivityChart.tsx (Server Component)
import { db } from '@/lib/db'
// async Server Component - 加载完成前显示fallback
export default async function ActivityChart() {
const data = await db.order.findMany({
where: { created_at: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } },
select: { created_at: true, total: true }
})
// 数据处理
const chartData = aggregateByDay(data)
return (
<div className="chart-container">
{chartData.map(d => (
<div key={d.date} className="bar" style={{height: `${d.value}px`}}>
{d.date}: {d.value}
</div>
))}
</div>
)
}
Suspense的fallback内容会立即作为HTML流式输出,当对应的数据加载完成后,React通过内联的script标签将真实内容注入到对应位置,无需额外的客户端JavaScript请求。
Server Actions与数据变更方案
Server Actions是RSC配套的数据变更机制,允许在Client Component中直接调用服务端函数,无需编写API路由。表单提交场景中Server Action可以无缝集成HTML form的action属性:
// app/products/create/CreateForm.tsx
'use client'
import { useFormState } from 'react-dom'
import { createProduct } from './actions'
export default function CreateForm() {
const [state, formAction] = useFormState(createProduct, null)
return (
<form action={formAction}>
<input name="name" type="text" required />
<input name="price" type="number" required />
<select name="category_id">
<option value="1">电子</option>
<option value="2">服装</option>
</select>
<button type="submit">创建</button>
{state?.error && <p className="error">{state.error}</p>}
{state?.success && <p className="success">创建成功</p>}
</form>
)
}
// app/products/create/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'
const ProductSchema = z.object({
name: z.string().min(1).max(100),
price: z.number().positive(),
category_id: z.number().int()
})
export async function createProduct(prevState, formData) {
const parsed = ProductSchema.safeParse({
name: formData.get('name'),
price: Number(formData.get('price')),
category_id: Number(formData.get('category_id'))
})
if (!parsed.success) {
return { error: parsed.error.issues[0].message }
}
const product = await db.product.create({
data: parsed.data
})
// 重新验证商品列表页缓存
revalidatePath('/products')
redirect(`/products/${product.id}`)
}
非表单场景中使用useTransition调用Server Action:
'use client'
import { useTransition } from 'react'
import { deleteProduct } from './actions'
export default function ProductRow({ product }) {
const [isPending, startTransition] = useTransition()
return (
<tr>
<td>{product.name}</td>
<td>{product.price}</td>
<td>
<button
disabled={isPending}
onClick={() => startTransition(() => deleteProduct(product.id))}
>
{isPending ? '删除中...' : '删除'}
</button>
</td>
</tr>
)
}
RSC缓存策略与数据预加载优化
Next.js App Router提供多层级缓存机制,合理配置可以显著提升页面加载速度:
// 1. 请求级记忆(Request Memoization)
// 同一请求中多次调用相同fetch会命中缓存
async function getProduct(id) {
const res = await fetch(`https://api.example.com/products/${id}`)
return res.json()
}
// 2. 数据缓存(Data Cache)
// fetch默认缓存,可配置revalidate
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1小时后重新验证
})
// 3. 全静态渲染(Static Rendering)
// 使用generateStaticParams预生成页面
export async function generateStaticParams() {
const products = await db.product.findMany()
return products.map(p => ({ id: String(p.id) }))
}
export default async function ProductPage({ params }) {
const product = await db.product.findUnique({ where: { id: Number(params.id) }})
return <ProductDetail product={product} />
}
// 4. ISR(增量静态再生)
export const revalidate = 60 // 每60秒后台重新生成
// 5. 路由段配置
export const dynamic = 'force-static' // 强制静态
export const fetchCache = 'force-no-store' // 禁用缓存
预加载(Prefetching)是RSC的性能优化利器。Next.js Link组件默认预加载目标路由的RSC Payload,用户点击前数据已在客户端就绪:
import Link from 'next/link'
// 默认prefetch=true,进入视口时自动预加载
<Link href="/products/123" prefetch={true}>
查看详情
</Link>
// 关闭预加载(低优先级页面)
<Link href="/terms" prefetch={false}>
服务条款
</Link>
// 手动预加载
import { useRouter } from 'next/navigation'
function ProductCard({ product }) {
const router = useRouter()
return (
<div
onMouseEnter={() => router.prefetch(`/products/${product.id}`)}
>
{product.name}
</div>
)
}
RSC Payload是Next.js协议格式的序列化数据,体积比JSON小30%-50%。在移动端弱网场景下,RSC Payload的流式传输相比传统SPA的JSON+JS加载方式,首屏内容可见时间可缩短40%以上。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-shi-zhan-liu-shi-xuan-ran-yu-shu-ju/