React Server Components实战:流式渲染与数据加载架构设计

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/

(0)
小编小编
上一篇 4小时前
下一篇 4小时前

相关推荐