前言

React Server Components (RSC) 是 React 团队在 2020 年底引入的革命性特性,Next.js 15 将这一特性推向了成熟。RSC 不仅仅是渲染方式的改变,更是前端开发范式的一次重大转移。

本文将从实现原理的角度,深入解析 Next.js 15 中 React Server Components 的工作机制,帮助您真正理解这一技术背后的设计哲学和最佳实践。

一、RSC 的核心概念

1.1 什么是 Server Components

Server Components 是一种特殊的 React 组件,它们:

  • 在服务器上渲染:完全不发送 JavaScript 到客户端
  • 直接访问后端资源:可以读取数据库、文件系统、内部 API
  • 不包含交互性:不能使用 hooks (useState, useEffect) 和事件处理器
  • 流式传输:支持渐进式渲染,可以逐步发送 UI 片段

1.2 客户端组件 vs 服务端组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ✅ Server Component (默认)
// 文件名: app/components/UserList.tsx
import { db } from '@/lib/db'

export default async function UserList() {
  // 可以直接访问数据库
  const users = await db.user.findMany()

  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  )
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ✅ Client Component
// 文件名: app/components/UserCard.tsx
'use client'

import { useState } from 'react'

export function UserCard({ userId }: { userId: string }) {
  // 可以使用 hooks
  const [liked, setLiked] = useState(false)

  return (
    <div>
      <button onClick={() => setLiked(!liked)}>
        {liked ? '❤️' : '🤍'}
      </button>
    </div>
  )
}

1.3 架构对比

传统 React 架构

┌─────────────────────────────────────────┐
│          Browser (Client)               │
│  ┌───────────────────────────────────┐  │
│  │  React App (JavaScript Bundle)    │  │
│  │  ├── Components                   │  │
│  │  ├── State Management             │  │
│  │  └── API Calls (fetch)            │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
              ↕ HTTP Request
┌─────────────────────────────────────────┐
│          Server (API)                   │
│  ┌───────────────────────────────────┐  │
│  │  Database                         │  │
│  │  File System                      │  │
│  │  Internal Services                │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

RSC 架构

┌─────────────────────────────────────────┐
│          Browser (Client)               │
│  ┌───────────────────────────────────┐  │
│  │  Client Components (Interactive)  │  │
│  │  ├── Event Handlers               │  │
│  │  ├── State (useState)             │  │
│  │  └── Effects (useEffect)          │  │
│  └───────────────────────────────────┘  │
│         ↕ (RSC Payload - JSON)          │
│  ┌───────────────────────────────────┐  │
│  │  React Server Runtime             │  │
│  │  (Reconstructs Server Tree)       │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
              ↕ HTTP Request
┌─────────────────────────────────────────┐
│          Server                         │
│  ┌───────────────────────────────────┐  │
│  │  Server Components                │  │
│  │  ├── Direct DB Access             │  │
│  │  ├── File System Access           │  │
│  │  └── Internal APIs                │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

二、RSC 渲染流程

2.1 完整渲染周期

1. Request
   ↓
2. Server renders Server Components to RSC Payload
   ↓
3. Server sends HTML + RSC Payload to Client
   ↓
4. Browser displays HTML (immediate paint)
   ↓
5. React hydrates Client Components
   ↓
6. React reconstructs Server Tree from RSC Payload
   ↓
7. Interactive

2.2 RSC Payload 格式

RSC Payload 是一种特殊的 JSON 格式,用于描述组件树:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
  "version": 1,
  "roots": {
    "0": "UserList"
  },
  "modules": {
    "0": {
      "name": "UserList",
      "exports": {
        "default": {
          "type": "server-component",
          "children": [
            {
              "type": "element",
              "name": "div",
              "props": {},
              "children": [
                {
                  "type": "client-component",
                  "module": "1",
                  "name": "UserCard",
                  "props": { "userId": "123" }
                }
              ]
            }
          ]
        }
      }
    },
    "1": {
      "name": "UserCard",
      "exports": {
        "default": {
          "type": "client-component"
        }
      }
    }
  }
}

2.3 流式渲染机制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 服务端代码
async function Page() {
  return (
    <div>
      <Header />                    {/* 立即渲染 */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataComponent />        {/* 延迟渲染,流式传输 */}
      </Suspense>
    </div>
  )
}

渲染时间线:

Time 0ms:   [HTML: <div><header>...</header><div id="loading">Loading...</div></div>]
            ↓ 发送到浏览器
            ↓ 浏览器立即显示

Time 1000ms: [RSC Patch: {"id": "slow-data", "content": "..."}]
             ↓ 追加到响应
             ↓ React 更新 DOM

Time 2000ms: [RSC Patch: {"id": "more-data", "content": "..."}]
             ↓ 继续流式更新

三、序列化机制

3.1 可序列化的数据

只有可序列化的数据才能从 Server Component 传递到 Client Component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ✅ 可序列化
const primitive = 'string'              // 基本类型
const array = [1, 2, 3]                 // 数组
const object = { name: 'John' }         // 纯对象
const date = new Date()                 // Date
const map = new Map([['key', 'value']]) // Map (需要配置)
const set = new Set([1, 2, 3])          // Set (需要配置)

// ❌ 不可序列化
const function = () => {}               // 函数
const classInstance = new MyClass()     // 类实例
const symbol = Symbol('id')             // Symbol

3.2 Props 传递规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Server Component
import { ClientComponent } from './ClientComponent'

export async function ServerComponent() {
  const data = await fetch('https://api.example.com/data').then(r => r.json())

  // ✅ 传递序列化数据
  return <ClientComponent data={data} count={data.length} />

  // ❌ 错误:传递函数
  // return <ClientComponent onClick={() => {}} />

  // ❌ 错误:传递复杂对象
  // return <ClientComponent db={db} />
}

3.3 使用 React Server Functions

对于需要传递函数的场景,使用 Server Actions:

1
2
3
4
5
6
7
8
// app/actions.ts
'use server'

export async function updateData(formData: FormData) {
  // 在服务器上执行
  const data = Object.fromEntries(formData)
  await db.update(data)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Client Component
'use client'

import { updateData } from '@/app/actions'

export function Form() {
  return (
    <form action={updateData}>
      <input name="field" />
      <button type="submit">Submit</button>
    </form>
  )
}

四、边界管理(The Boundary)

4.1 理解组件边界

边界是 Server Components 和 Client Components 之间的分界线:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Server Component (默认)
export default async function Page() {
  const data = await fetchData()

  return (
    <div>
      {/* Server Context */}
      <ServerOnly data={data} />

      {/* ⚠️ 边界:开始 Client Component */}
      <ClientWrapper>
        {/* Client Context */}
      </ClientWrapper>
    </div>
  )
}

4.2 边界规则

规则 1:Server Component 可以导入 Client Component

1
2
3
4
5
6
7
// ✅ 正确
// app/page.tsx (Server Component)
import { InteractiveButton } from '@/components/InteractiveButton' // Client Component

export default function Page() {
  return <InteractiveButton />
}

规则 2:Client Component 不能导入 Server Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ❌ 错误
// components/InteractiveButton.tsx (Client Component)
'use client'

import { ServerData } from './ServerData' // ❌ Server Component

export function InteractiveButton() {
  return (
    <div>
      <ServerData />  {/* ❌ 无法在客户端渲染 */}
    </div>
  )
}

规则 3:通过 Children Props 穿透边界

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ✅ 正确模式
// components/ServerLayout.tsx (Server Component)
import { ClientProvider } from './ClientProvider'

export async function ServerLayout({ children }: {
  children: React.ReactNode
}) {
  const data = await fetchData()

  return (
    <ClientProvider initialData={data}>
      {children}  {/* children 可以是 Server Component */}
    </ClientProvider>
  )
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// components/ClientProvider.tsx (Client Component)
'use client'

import { useState } from 'react'

export function ClientProvider({
  initialData,
  children
}: {
  initialData: any
  children: React.ReactNode
}) {
  const [data, setData] = useState(initialData)

  return (
    <DataContext.Provider value={{ data, setData }}>
      {children}  {/* Server Component 在这里渲染 */}
    </DataContext.Provider>
  )
}

4.3 边界最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ✅ 最佳实践:边界下移
// 将交互性隔离到最小的客户端组件中

// app/blog/[slug]/page.tsx (Server Component)
export default async function BlogPost({ params }: {
  params: { slug: string }
}) {
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  })

  return (
    <article>
      <header>
        <h1>{post.title}</h1>
        <p>{post.excerpt}</p>
      </header>

      {/* Server Component:静态内容 */}
      <Content html={post.content} />

      {/* Client Component:交互功能 */}
      <LikeButton postId={post.id} />
      <CommentSection postId={post.id} />
    </article>
  )
}

// components/LikeButton.tsx (Client Component)
'use client'

import { useState } from 'react'

export function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false)

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  )
}

五、数据获取模式

5.1 直接数据库访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// app/users/page.tsx
import { db } from '@/lib/db'

export const revalidate = 3600  // ISR: 1小时

export default async function UsersPage() {
  // Server Component 可以直接访问数据库
  const users = await db.user.findMany({
    include: {
      posts: {
        select: { id: true, title: true }
      }
    }
  })

  return (
    <div>
      <h1>用户列表</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} ({user.posts.length} 篇文章)
          </li>
        ))}
      </ul>
    </div>
  )
}

5.2 并行数据获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// app/dashboard/page.tsx
export default async function DashboardPage() {
  // 并行获取多个数据源
  const [stats, posts, comments] = await Promise.all([
    getStats(),
    getRecentPosts(),
    getRecentComments()
  ])

  return (
    <div>
      <Stats data={stats} />
      <RecentPosts posts={posts} />
      <RecentComments comments={comments} />
    </div>
  )
}

5.3 条件数据获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// app/profile/page.tsx
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'

export default async function ProfilePage() {
  const session = await getServerSession()

  if (!session) {
    redirect('/login')
  }

  // 只在登录后获取数据
  const profile = await db.profile.findUnique({
    where: { userId: session.user.id }
  })

  return <ProfileView profile={profile} />
}

5.4 流式数据获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// app/dashboard/page.tsx
import { Suspense } from 'react'

export default function DashboardPage() {
  return (
    <div>
      <h1>仪表板</h1>

      {/* 立即渲染 */}
      <WelcomeMessage />

      {/* 并行加载,各自独立流式传输 */}
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />
      </Suspense>

      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<ActivitySkeleton />}>
        <RecentActivity />
      </Suspense>
    </div>
  )
}

// components/Stats.tsx
export async function Stats() {
  // 这个请求不会阻塞其他部分
  const stats = await fetch('https://api.example.com/stats', {
    cache: 'no-store'
  }).then(r => r.json())

  return <StatsView data={stats} />
}

六、缓存策略

6.1 fetch 缓存

1
2
3
4
5
6
7
// 默认缓存:force-cache (缓存直到手动重新验证)
export default async function Page() {
  const data = await fetch('https://api.example.com/data')
    .then(r => r.json())

  return <View data={data} }
}
1
2
3
4
5
6
7
8
// no-store:每次都重新获取
export default async function Page() {
  const data = await fetch('https://api.example.com/data', {
    cache: 'no-store'
  }).then(r => r.json())

  return <View data={data} />
}
1
2
3
4
5
6
7
8
// next.revalidate:指定重新验证时间(秒)
export default async function Page() {
  const data = await fetch('https://api.example.com/data', {
    next: { revalidate: 60 }  // 60秒后重新验证
  }).then(r => r.json())

  return <View data={data} />
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// next.tags:标签化的重新验证
export default async function Page() {
  const data = await fetch('https://api.example.com/data', {
    next: { tags: ['posts'] }  // 可以通过标签重新验证
  }).then(r => r.json())

  return <View data={data} />
}

// 在 Server Action 中重新验证
import { revalidateTag } from 'next/cache'

export async function updatePost() {
  // 更新数据
  await update()

  // 重新验证所有带有 'posts' 标签的缓存
  revalidateTag('posts')
}

6.2 路由级缓存

1
2
3
4
5
6
7
8
9
// app/products/page.tsx

// 整个路由的重新验证时间
export const revalidate = 3600  // 1小时

export default async function ProductsPage() {
  const products = await getProducts()
  return <ProductsList products={products} />
}

6.3 客户端导航缓存

1
2
3
4
// app/layout.tsx
import { unstable_cacheLife as cacheLife } from 'next/cache'

export const fetchCache = cacheLife('max')

七、错误处理

7.1 错误边界

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// app/blog/error.tsx
'use client'  // 错误边界必须是客户端组件

export default function BlogError({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div className="error-container">
      <h2>出错了!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>重试</button>
    </div>
  )
}

7.2 全局错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>发生严重错误!</h2>
        <button onClick={reset}>重试</button>
      </body>
    </html>
  )
}

八、性能优化技巧

8.1 减少客户端 JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ✅ 优化前:整个组件树都是客户端组件
'use client'

export function BlogPage() {
  const [posts, setPosts] = useState([])

  useEffect(() => {
    fetch('/api/posts').then(r => r.json()).then(setPosts)
  }, [])

  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  )
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ✅ 优化后:只有交互部分是客户端组件
export default async function BlogPage() {
  const posts = await db.post.findMany()

  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post}>
          {/* 只有 LikeButton 是客户端组件 */}
          <LikeButton postId={post.id} />
        </PostCard>
      ))}
    </div>
  )
}

8.2 动态导入重型组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// app/dashboard/page.tsx
import dynamic from 'next/dynamic'

// 仅客户端加载的重型组件
const HeavyChart = dynamic(
  () => import('@/components/HeavyChart'),
  {
    loading: () => <ChartSkeleton />,
    ssr: false  // 禁用 SSR
  }
)

export default function DashboardPage() {
  return (
    <div>
      <h1>仪表板</h1>
      <HeavyChart />
    </div>
  )
}

8.3 代码分割策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// components/ClientOnly.tsx
'use client'

import { useEffect, useState } from 'react'

export function ClientOnly({ children }: { children: React.ReactNode }) {
  const [hasMounted, setHasMounted] = useState(false)

  useEffect(() => {
    setHasMounted(true)
  }, [])

  if (!hasMounted) {
    return null
  }

  return <>{children}</>
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 使用
import { ClientOnly } from '@/components/ClientOnly'
import { HeavyComponent } from '@/components/HeavyComponent'

export default function Page() {
  return (
    <div>
      <h1>标题</h1>

      <ClientOnly>
        <HeavyComponent />
      </ClientOnly>
    </div>
  )
}

九、测试策略

9.1 测试 Server Components

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// __tests__/UserList.test.tsx
import { renderToString } from 'react-dom/server'
import { UserList } from '@/components/UserList'

// Mock 数据库
jest.mock('@/lib/db')

describe('UserList', () => {
  it('should render user list', async () => {
    const mockUsers = [
      { id: '1', name: 'Alice' },
      { id: '2', name: 'Bob' }
    ]

    db.user.findMany.mockResolvedValue(mockUsers)

    const html = renderToString(await UserList())

    expect(html).toContain('Alice')
    expect(html).toContain('Bob')
  })
})

9.2 测试 Client Components

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// __tests__/LikeButton.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { LikeButton } from '@/components/LikeButton'

describe('LikeButton', () => {
  it('should toggle like status', () => {
    render(<LikeButton postId="123" />)

    const button = screen.getByRole('button')

    expect(button).toHaveTextContent('🤍')

    fireEvent.click(button)

    expect(button).toHaveTextContent('❤️')
  })
})

十、迁移指南

10.1 从 Pages Router 迁移

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// pages/users.tsx (旧)
import { GetServerSideProps } from 'next'

export default function UsersPage({ users }) {
  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  )
}

export const getServerSideProps: GetServerSideProps = async () => {
  const users = await db.user.findMany()
  return { props: { users } }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// app/users/page.tsx (新)
import { db } from '@/lib/db'

export default async function UsersPage() {
  // 直接在组件中获取数据
  const users = await db.user.findMany()

  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  )
}

10.2 从客户端状态迁移

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 旧:全客户端
'use client'

export function ProductsPage() {
  const [products, setProducts] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/products')
      .then(r => r.json())
      .then(data => {
        setProducts(data)
        setLoading(false)
      })
  }, [])

  if (loading) return <div>Loading...</div>

  return <ProductList products={products} />
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 新:服务器渲染 + 客户端增强
export default async function ProductsPage() {
  // 服务器端预取数据
  const initialProducts = await db.product.findMany()

  return (
    <div>
      <ProductList products={initialProducts} />
      <ProductFilters />  {/* 客户端筛选功能 */}
    </div>
  )
}

结语

React Server Components 代表了前端开发的一次范式转移。通过理解其核心概念和实现机制,您可以:

  1. 减少客户端 JavaScript:只在需要交互时使用客户端组件
  2. 提升性能:利用服务器渲染和流式传输
  3. 简化数据获取:直接访问后端资源
  4. 优化用户体验:更快的首屏加载和更流畅的交互

关键要点:

  • Server Components 是默认选择
  • Client Components 只在需要交互时使用
  • 通过 Children Props 穿透边界
  • 利用流式渲染提升感知性能
  • 合理使用缓存策略

下一步学习:

开始使用 Server Components 构建更快的 Web 应用吧!


延伸阅读: