React Server Components运行机制与渲染模型
React Server Components(RSC)是React 18引入的服务端组件架构,核心思想是将组件分为Server Components和Client Components两类。Server Components在服务端执行,不打包到客户端JS bundle中,可直接访问数据库和文件系统;Client Components通过"use client"指令声明,在客户端执行并支持交互。
RSC的渲染流程分为三阶段:服务端渲染Server Components生成RSC Payload(序列化的组件树),客户端接收Payload后渲染, hydration阶段激活Client Components。与传统SSR的区别在于,RSC的HTML仅为初始骨架,交互逻辑由Client Components按需加载。
// app/layout.tsx - 根布局(Server Component,默认)
import { db } from '@/lib/database'
import Header from '@/components/Header'
import { Analytics } from '@/components/Analytics'
// 直接在服务端查询数据库,无需API层
async function getSiteConfig() {
const config = await db.query('SELECT * FROM site_config WHERE id = 1')
return config
}
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const config = await getSiteConfig()
return (
<html lang="zh-CN">
<body>
<Header title={config.title} />
{children}
{/* Client Component通过'use client'自动识别 */}
<Analytics trackingId={config.analyticsId} />
</body>
</html>
)
}
Server Components数据获取模式
RSC支持直接在组件中async/await获取数据,消除了传统React中useEffect + fetch + loading state的繁琐模式。Next.js App Router在此基础上提供了缓存和重新验证机制:
// app/products/page.tsx - Server Component数据获取
import { db } from '@/lib/database'
import { cache } from 'react'
import ProductCard from '@/components/ProductCard'
import Pagination from '@/components/Pagination'
import { notFound } from 'next/navigation'
// 使用cache()函数实现请求级去重
const getProductList = cache(async (page: number, pageSize: number) => {
const [products, total] = await Promise.all([
db.query(
'SELECT id, name, price, image_url FROM products ORDER BY created_at DESC LIMIT ? OFFSET ?',
[pageSize, (page - 1) * pageSize]
),
db.query('SELECT COUNT(*) as total FROM products WHERE status = 1')
])
return { products, total: total[0].total }
})
export default async function ProductsPage({
searchParams,
}: {
searchParams: { page?: string }
}) {
const page = Number(searchParams.page) || 1
const pageSize = 20
// 并行获取数据
const { products, total } = await getProductList(page, pageSize)
if (products.length === 0 && page > 1) {
notFound()
}
return (
<div className="product-grid">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
<Pagination
currentPage={page}
totalPages={Math.ceil(total / pageSize)}
/>
</div>
)
}
// 生成静态页面参数(ISR)
export async function generateStaticParams() {
const total = await db.query('SELECT COUNT(*) as total FROM products WHERE status = 1')
const totalPages = Math.ceil(total[0].total / 20)
return Array.from({ length: Math.min(totalPages, 10) }, (_, i) => ({
page: String(i + 1),
}))
}
// 配置页面缓存策略
export const revalidate = 300 // 每5分钟重新验证
export const dynamic = 'force-dynamic' // 或 'force-static' | 'auto'
cache()函数在单次请求内去重相同参数的调用,跨请求缓存依赖Next.js的revalidate配置。对于实时性要求高的数据,使用export const revalidate = 0或dynamic = 'force-dynamic'禁用缓存。
Server与Client Components的边界划分
Server Components不能使用useState、useEffect、事件处理等客户端特性。当组件需要交互时,通过"use client"声明为Client Component,但需注意客户端边界应尽可能缩小以减少bundle体积:
// components/ProductCard.tsx - Server Component(默认)
// 纯展示组件,在服务端渲染,不打包到客户端
export default function ProductCard({ product }: { product: Product }) {
return (
<div className="card">
<img src={product.image_url} alt={product.name} />
<h3>{product.name}</h3>
<p className="price">¥{product.price}</p>
{/* 交互部分提取为独立Client Component */}
<AddToCartButton productId={product.id} />
</div>
)
}
// components/AddToCartButton.tsx - Client Component
'use client'
import { useState } from 'react'
import { addToCart } from '@/lib/cart'
export default function AddToCartButton({ productId }: { productId: number }) {
const [loading, setLoading] = useState(false)
const [added, setAdded] = useState(false)
const handleClick = async () => {
setLoading(true)
await addToCart(productId)
setLoading(false)
setAdded(true)
setTimeout(() => setAdded(false), 2000)
}
return (
<button
onClick={handleClick}
disabled={loading}
className="btn-cart"
>
{loading ? '添加中...' : added ? '已添加' : '加入购物车'}
</button>
)
}
Server与Client Components之间的数据传递通过props完成,但传递的数据必须可序列化(不能传递函数、Class实例、Symbol等):
// 错误:不能向Client Component传递函数
<ClientComponent onData={async () => await fetchData()} />
// 正确:传递序列化数据,Client Component自行fetch
<ClientComponent initialData={serializedData} />
// 正确:通过Server Action触发服务端操作
<ClientComponent action={serverAction} />
Server Actions与表单处理
Server Actions是RSC架构中的服务端函数,可直接在Client Components中调用,实现无API路由的服务端操作。表单提交是典型应用场景:
// app/contact/actions.ts
'use server'
import { db } from '@/lib/database'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function submitContactForm(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
const message = formData.get('message') as string
// 服务端验证
if (!name || !email || !message) {
return { error: '请填写所有字段' }
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return { error: '邮箱格式不正确' }
}
// 写入数据库
await db.execute(
'INSERT INTO contacts (name, email, message, created_at) VALUES (?, ?, ?, NOW())',
[name, email, message]
)
// 重新验证相关页面缓存
revalidatePath('/admin/contacts')
redirect('/contact/success')
}
// app/contact/page.tsx - 表单组件
import { submitContactForm } from './actions'
import { useFormState } from 'react-dom'
export default function ContactPage() {
const [state, formAction] = useFormState(submitContactForm, null)
return (
<form action={formAction}>
<input type="text" name="name" placeholder="姓名" required />
<input type="email" name="email" placeholder="邮箱" required />
<textarea name="message" placeholder="留言" required />
{state?.error && <p className="error">{state.error}</p>}
<button type="submit">提交</button>
</form>
)
}
Streaming SSR与Suspense集成
RSC支持流式渲染(Streaming SSR),配合Suspense实现渐进式页面加载。慢速数据请求不会阻塞整个页面,先渲染可快速完成的部分,慢请求部分通过Suspense fallback占位:
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { Metrics } from '@/components/Metrics'
import { RecentOrders } from '@/components/RecentOrders'
import { ActivityFeed } from '@/components/ActivityFeed'
export default function DashboardPage() {
return (
<div className="dashboard">
{/* 快速加载的组件立即渲染 */}
<Suspense fallback={<div className="skeleton">加载指标...</div>}>
<Metrics />
</Suspense>
{/* 慢速组件通过Suspense fallback占位 */}
<Suspense fallback={<OrderSkeleton />}>
<RecentOrders />
</Suspense>
<Suspense fallback={<ActivitySkeleton />}>
<ActivityFeed />
</Suspense>
</div>
)
}
// components/Metrics.tsx - Server Component
import { db } from '@/lib/database'
import { cache } from 'react'
const getMetrics = cache(async () => {
// 慢查询,可能需要2-3秒
const result = await db.query(`
SELECT
COUNT(*) as total_orders,
SUM(amount) as total_revenue,
AVG(amount) as avg_order_value
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
`)
return result[0]
})
export async function Metrics() {
const metrics = await getMetrics()
return (
<div className="metrics-grid">
<div className="metric">
<span className="label">总订单</span>
<span className="value">{metrics.total_orders}</span>
</div>
<div className="metric">
<span className="label">总收入</span>
<span className="value">¥{metrics.total_revenue.toLocaleString()}</span>
</div>
</div>
)
}
Streaming SSR将HTML分块发送到浏览器,首屏内容(如导航栏、快速加载组件)在慢请求完成前即可渲染。结合React 18的Selective Hydration,客户端hydration也按Suspense边界分批进行,避免长任务阻塞主线程。
性能监控方面,RSC架构下需关注的指标包括RSC Payload大小(影响首屏加载)、Client Components bundle大小(影响hydration)、以及Server Component执行时间(影响TTFB)。使用Next.js内置的@next/bundle-analyzer分析Client Components体积,配合next build的RSC输出日志定位可优化为Server Components的客户端组件。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-shi-zhan-fu-wu-duan-xuan-ran-jia-gou/