React Server Components(RSC)从根本上改变了React应用的渲染模型,将组件分为Server Components和Client Components两类,服务端组件在服务器执行不携带JavaScript运行时代码到客户端。前端开发领域对渲染性能的追求推动了RSC的出现。本文通过实际项目演示RSC的架构设计、数据获取模式、流式渲染机制以及与Client Components的协作方式。
Server Components与Client Components边界划分
RSC的核心原则是”默认服务端,按需客户端”。Server Components可以直接访问数据库、文件系统等服务端资源,不增加客户端bundle大小。Client Components通过”use client”指令声明,拥有状态管理和事件处理能力。
// app/layout.tsx - 根布局(Server Component)
import { Suspense } from 'react'
import Header from '@/components/Header'
import { ProductList } from '@/components/ProductList'
export default async function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body>
<Header />
<Suspense fallback={<div>加载中...</div>}>
<ProductList />
</Suspense>
{children}
</body>
</html>
)
}
// app/components/ProductList.tsx(Server Component)
import { db } from '@/lib/database'
export async function ProductList() {
// 直接在服务端查询数据库,零客户端JS
const products = await db.product.findMany({
include: { category: true },
take: 20
})
return (
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<div key={product.id}>
<h3>{product.name}</h3>
<span>{product.category.name}</span>
<AddToCartButton productId={product.id} />
</div>
))}
</div>
)
}
// app/components/AddToCartButton.tsx(Client Component)
'use client'
import { useState } from 'react'
export function AddToCartButton({ productId }: { productId: number }) {
const [loading, setLoading] = useState(false)
const [added, setAdded] = useState(false)
async function handleClick() {
setLoading(true)
try {
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
})
setAdded(true)
} finally {
setLoading(false)
}
}
return (
<button
onClick={handleClick}
disabled={loading || added}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
{loading ? '添加中...' : added ? '已添加' : '加入购物车'}
</button>
)
}
边界划分原则:数据展示类组件使用Server Component(如列表、详情页、布局组件),交互类组件使用Client Component(如按钮、表单、弹窗)。Server Component可以导入Client Component,但Client Component不能导入Server Component——它们只能通过props接收Server Component渲染结果作为children。
流式渲染与Suspense数据加载策略
RSC支持流式渲染,通过Suspense边界将页面拆分为多个独立的渲染流,先完成的部分立即发送到客户端,慢部分在就绪后以流式方式注入。
// app/dashboard/page.tsx - 流式渲染实战
import { Suspense } from 'react'
// 快速加载组件
async function UserProfile() {
const user = await fetch('https://api.example.com/user', {
cache: 'force-cache'
}).then(r => r.json())
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}
// 慢速加载组件 - 实时数据分析
async function RevenueChart() {
// 这个查询可能需要2-3秒
const data = await fetch('https://api.example.com/analytics/revenue', {
cache: 'no-store' // 不缓存,每次请求都获取最新数据
}).then(r => r.json())
return (
<div className="chart">
{/* 图表渲染 */}
</div>
)
}
// 中速加载组件
async function RecentOrders() {
const orders = await fetch('https://api.example.com/orders?limit=10', {
next: { revalidate: 60 } // 60秒ISR缓存
}).then(r => r.json())
return (
<ul>
{orders.map(order => (
<li key={order.id}>{order.id} - {order.total}</li>
))}
</ul>
)
}
export default function DashboardPage() {
return (
<main>
{/* 立即渲染,快速响应 */}
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile />
</Suspense>
{/* 异步流式注入 */}
<Suspense fallback={<SkeletonChart />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<SkeletonList />}>
<RecentOrders />
</Suspense>
</main>
)
}
fetch函数在RSC中扩展了cache和next选项。cache: ‘force-cache’等效于静态生成,适合不常变的数据;cache: ‘no-store’表示每次请求都获取最新数据;next: { revalidate: 60 }实现ISR(增量静态再生成),60秒内用缓存,超过后后台刷新。
Server Actions与表单处理机制
Server Actions允许在Server Component中定义服务端执行函数,通过表单提交或直接调用触发,无需创建API路由。
// app/actions/contact.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'
const ContactSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
message: z.string().min(10).max(1000)
})
export async function submitContact(formData: FormData) {
const validated = ContactSchema.parse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message')
})
// 写入数据库
await db.contact.create({
data: validated
})
// 重新验证相关页面缓存
revalidatePath('/contact')
redirect('/contact/success')
}
// app/contact/page.tsx
import { submitContact } from '@/app/actions/contact'
export default function ContactPage() {
return (
<form action={submitContact}>
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">提交</button>
</form>
)
}
Server Actions通过加密的action ID绑定,防止跨站请求伪造。revalidatePath在数据变更后自动刷新受影响页面的静态缓存,保持数据一致性。
缓存层级与数据获取优化策略
RSC架构包含四层缓存机制:请求级memoization(同一渲染周期内重复请求自动去重)、数据缓存(fetch缓存,跨请求持久)、全路由缓存(构建时预渲染的静态页面)、路由缓存(客户端导航时的RSC payload缓存)。
// 请求级memoization - 同一请求中重复调用自动去重
import { cache } from 'react'
// 使用cache()包装,同一渲染周期内相同参数只执行一次
const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } })
})
async function UserHeader({ userId }: { userId: string }) {
const user = await getUser(userId)
return <header>{user.name}</header>
}
async function UserSidebar({ userId }: { userId: string }) {
const user = await getUser(userId) // 自动命中缓存
return <aside>{user.role}</aside>
}
// 数据缓存与标签化失效
import { revalidateTag } from 'next/cache'
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { tags: [`product-${id}`] }
})
return res.json()
}
// 当产品数据更新时,按标签失效缓存
export async function updateProduct(id: string, data: any) {
await fetch(`https://api.example.com/products/${id}`, {
method: 'PUT',
body: JSON.stringify(data)
})
revalidateTag(`product-${id}`)
}
cache()函数实现的是请求级别的memoization,在React的并发渲染中,多个组件共享同一数据的请求会被自动去重。revalidateTag按标签粒度精确失效缓存,避免全站revalidate带来的性能开销。部署时需注意App Router的路由段配置,静态路由在构建时预渲染,动态路由按需渲染。对于混合渲染策略,可在路由段配置中通过export const dynamic = ‘force-static’或’force-dynamic’显式指定渲染模式。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-fu-wu-duan-xuan-ran-jia-gou-yu-liu/