React Server Components数据获取与流式渲染机制解析

React Server Components(RSC)是React 18引入的服务端组件架构,Next.js App Router将其落地为可用的开发模式。RSC将组件渲染拆分到服务端和客户端:Server Components在服务端执行可直接访问数据库和文件系统,Client Components在浏览器执行处理交互逻辑。理解RSC的数据获取机制、流式渲染和组件边界划分,是前端工程化中构建高性能React应用的关键,Web性能优化因此进入新阶段。

Server Components与Client Components的边界划分

App Router中默认所有组件都是Server Components。需要浏览器交互(useState、onClick、useEffect等)的组件通过”use client”指令声明为Client Components。边界划分原则:尽量将组件树保持为Server Components,只在需要交互的叶子节点切换为Client Components。

// app/products/page.tsx (Server Component)
import { db } from '@/lib/db'
import ProductFilter from './ProductFilter'

export default async function ProductsPage({
  searchParams,
}: {
  searchParams: { category?: string }
}) {
  const products = await db.query(
    'SELECT * FROM products WHERE category = $1',
    [searchParams.category || 'all']
  )

  return (
    <div>
      <ProductFilter initialCategory={searchParams.category} />
      <ProductList products={products} />
    </div>
  )
}

// app/products/ProductFilter.tsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

export default function ProductFilter({ initialCategory }: {
  initialCategory?: string
}) {
  const router = useRouter()
  const [category, setCategory] = useState(initialCategory || 'all')

  const handleChange = (value: string) => {
    setCategory(value)
    router.push(`/products?category=${value}`)
  }

  return (
    <select value={category} onChange={(e) => handleChange(e.target.value)}>
      <option value="all">全部</option>
      <option value="electronics">电子产品</option>
    </select>
  )
}

Server Components不能使用useState、useEffect、浏览器API。Client Components不能直接使用async/await获取数据,需通过props传入或使用SWR/React Query。

Server Components中的数据获取模式

RSC中数据获取在服务端完成,组件可直接使用async/await。Next.js 14支持在Server Components中直接访问数据库、调用内部服务,省去API中间层。

// app/dashboard/page.tsx
import { db } from '@/lib/db'
import { Suspense } from 'react'

export default async function Dashboard() {
  const user = await getUser()

  // Promise.all并行请求无依赖数据
  const [orders, revenue, cacheData] = await Promise.all([
    db.query('SELECT COUNT(*) FROM orders WHERE user_id = $1', [user.id]),
    db.query('SELECT SUM(amount) FROM payments WHERE user_id = $1', [user.id]),
    redis.get(`user:${user.id}:cache`),
  ])

  return (
    <main>
      <Suspense fallback={<MetricsCard.Skeleton />}>
        <MetricsCard
          orderCount={orders.rows[0].count}
          revenue={revenue.rows[0].sum}
        />
      </Suspense>
    </main>
  )
}

Suspense包裹的组件会流式渲染:先返回已就绪的HTML,异步数据准备好后再通过流式响应推送给浏览器。用户看到页面渐进式填充而非白屏等待。

嵌套Suspense实现渐进式流式渲染

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <div>
      <Suspense fallback={<HeaderSkeleton />}>
        <Header />
      </Suspense>

      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>

      <Suspense fallback={<MainSkeleton />}>
        {children}
      </Suspense>
    </div>
  )
}

每个Suspense边界独立流式渲染,Header先就绪先返回,Sidebar和主内容区域在数据加载后陆续填充。这种模式使首屏时间(FCP)大幅缩短——用户不需要等待所有数据加载完毕才看到页面内容。

数据请求缓存与重新验证策略

Next.js App Router的fetch自动缓存机制替代了getStaticProps和getServerSideProps。通过cache和next.revalidate参数控制缓存行为。

// 模式1: 静态生成(构建时获取,永久缓存)
async function getCategories() {
  const res = await fetch('https://api.example.com/categories', {
    cache: 'force-cache'
  })
  return res.json()
}

// 模式2: ISR增量静态再生成
async function getArticle(slug: string) {
  const res = await fetch(`https://api.example.com/articles/${slug}`, {
    next: { revalidate: 3600 }
  })
  return res.json()
}

// 模式3: 动态渲染(每次请求都获取)
async function getUserStats(userId: string) {
  const res = await fetch(`https://api.example.com/users/${userId}/stats`, {
    cache: 'no-store'
  })
  return res.json()
}

// 模式4: 按需重新验证
import { revalidateTag, revalidatePath } from 'next/cache'

export async function POST(request: Request) {
  const { slug } = await request.json()
  revalidatePath(`/blog/${slug}`)
  revalidateTag('articles')
  return Response.json({ revalidated: true })
}

Server Actions实现表单提交与数据变更

Server Actions让Client Components直接调用服务端函数处理表单提交和数据变更,无需编写API路由。Server Actions在服务端执行,自动处理CSRF防护。

// app/todos/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'

export async function createTodo(formData: FormData) {
  const title = formData.get('title') as string
  await db.query('INSERT INTO todos (title) VALUES ($1)', [title])
  revalidatePath('/todos')
}

// app/todos/TodoForm.tsx
'use client'
import { useFormStatus, useActionState } from 'react'
import { createTodo } from './actions'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '添加'}
    </button>
  )
}

export default function TodoForm() {
  const [state, formAction] = useActionState(createTodo, { error: null })
  return (
    <form action={formAction}>
      <input name="title" type="text" />
      {state?.error && <p className="error">{state.error}</p>}
      <SubmitButton />
    </form>
  )
}

useFormStatus自动跟踪表单提交状态,useActionState处理返回结果和错误。表单提交后revalidatePath自动刷新页面数据,无需手动refetch。

RSC性能优化实践要点

1. 减少Client Components的props序列化体积。Server Components传给Client Components的props会被序列化为JSON,避免传递大对象或Date/Map等不可序列化类型。只传必要字段而非整个ORM对象。

2. 将获取数据的代码放在组件树的尽可能深处,利用嵌套Suspense实现局部流式渲染。避免在根layout中获取所有数据导致整个页面阻塞。

3. 使用next/dynamic延迟加载Client Components,减少首屏JavaScript体积:

import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <div style={{ height: 400 }}>加载中...</div>,
  ssr: false
})

4. 使用optimizePackageImports优化大型组件库(如lucide-react、antd)的按需导入,减少打包体积。在next.config.js中配置experimental.optimizePackageImports即可。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-shu-ju-huo-qu-yu-liu-shi-xuan-ran-ji/

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

相关推荐