React Server Components实战:服务端渲染架构与数据获取模式详解

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 = 0dynamic = '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/

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

相关推荐