Skip to content

React Server Components 原理

RSC 是 React 19 的核心特性,2025 面试必考

1. 什么是 Server Components?

1.1 核心概念

Server Components = 只在服务端运行的 React 组件,不发送任何 JS 到客户端

┌─────────────────────────────────────────────────┐
│              组件类型对比                         │
├─────────────────────────────────────────────────┤
│                                                 │
│  Server Component (默认)                        │
│  ├── 在服务端执行                                │
│  ├── 可以直接访问数据库、文件系统                 │
│  ├── 不发送 JS 到客户端 (零 Bundle)              │
│  └── 不能使用 useState、useEffect、事件处理      │
│                                                 │
│  Client Component ('use client')                │
│  ├── 在客户端执行(也可 SSR)                    │
│  ├── 可以使用所有 Hooks                         │
│  ├── 可以添加交互(onClick 等)                  │
│  └── JS 发送到客户端                            │
│                                                 │
└─────────────────────────────────────────────────┘

1.2 基本示例

jsx
// app/page.tsx - Server Component (默认)
async function BlogPage() {
    // 直接访问数据库!
    const posts = await db.query('SELECT * FROM posts');
    
    return (
        <div>
            <h1>Blog</h1>
            {posts.map(post => (
                <article key={post.id}>
                    <h2>{post.title}</h2>
                    <p>{post.summary}</p>
                    <LikeButton postId={post.id} />  {/* Client Component */}
                </article>
            ))}
        </div>
    );
}

// components/LikeButton.tsx - Client Component
'use client';

import { useState } from 'react';

export function LikeButton({ postId }) {
    const [likes, setLikes] = useState(0);
    
    return (
        <button onClick={() => setLikes(l => l + 1)}>
            ❤️ {likes}
        </button>
    );
}

2. 为什么需要 Server Components?

2.1 传统 CSR 的问题

┌─────────────────────────────────────────────────┐
│              传统 CSR 问题                        │
├─────────────────────────────────────────────────┤
│                                                 │
│  Bundle Size 爆炸:                              │
│  ├── React + ReactDOM: ~40KB                   │
│  ├── 状态管理: ~10-30KB                         │
│  ├── UI 组件库: ~50-100KB                       │
│  ├── 工具库 (date-fns, lodash): ~20-50KB       │
│  ├── 业务代码: ~100KB+                          │
│  └── 总计: 300-500KB (gzip)                    │
│                                                 │
│  数据获取瀑布:                                   │
│  Download HTML → Download JS → Execute JS      │
│      → Fetch API → Render                      │
│      (串行,每步都要等)                          │
│                                                 │
└─────────────────────────────────────────────────┘

2.2 Server Components 解决方案

┌─────────────────────────────────────────────────┐
│           Server Components 优势                 │
├─────────────────────────────────────────────────┤
│                                                 │
│  1. 零 Bundle Size                              │
│     Server Component 不发送 JS                  │
│     只发送渲染结果 (HTML + Flight 数据)          │
│                                                 │
│  2. 直接数据访问                                 │
│     无需 API 层,直接访问数据库                   │
│     减少网络往返                                 │
│                                                 │
│  3. 自动代码分割                                 │
│     Client Component 自动按需加载               │
│                                                 │
│  4. 安全性                                      │
│     敏感逻辑在服务端,不暴露给客户端             │
│                                                 │
└─────────────────────────────────────────────────┘

3. 'use client' 和 'use server' 指令

3.1 'use client' - 客户端边界

jsx
// 文件顶部添加 'use client'
'use client';

// 这个文件及其导入的所有组件都是 Client Component
import { useState } from 'react';
import { Button } from './Button';  // 也变成 Client Component

export function Counter() {
    const [count, setCount] = useState(0);
    return <Button onClick={() => setCount(c => c + 1)}>{count}</Button>;
}

3.2 'use server' - Server Actions

jsx
// actions.ts
'use server';

// 这个文件的所有导出函数都是 Server Actions
export async function createPost(formData: FormData) {
    const title = formData.get('title') as string;
    await db.posts.create({ title });
    revalidatePath('/posts');
}

export async function deletePost(id: string) {
    await db.posts.delete(id);
    revalidatePath('/posts');
}

3.3 边界规则

┌─────────────────────────────────────────────────┐
│              组件边界规则                         │
├─────────────────────────────────────────────────┤
│                                                 │
│  Server Component 可以:                         │
│  ├── import Client Component ✅                 │
│  ├── import Server Component ✅                 │
│  └── 将 Server Component 作为 children 传递 ✅  │
│                                                 │
│  Client Component 可以:                         │
│  ├── import Client Component ✅                 │
│  └── 接收 Server Component 作为 children ✅     │
│                                                 │
│  Client Component 不能:                         │
│  └── import Server Component ❌                 │
│                                                 │
└─────────────────────────────────────────────────┘
jsx
// ✅ 正确:Server 导入 Client
// ServerComponent.tsx (Server)
import { ClientButton } from './ClientButton';

export function ServerComponent() {
    return <ClientButton />;
}

// ✅ 正确:通过 children 传递
// ClientWrapper.tsx
'use client';
export function ClientWrapper({ children }) {
    return <div onClick={...}>{children}</div>;  // children 可以是 Server Component
}

// Page.tsx (Server)
import { ClientWrapper } from './ClientWrapper';
import { ServerContent } from './ServerContent';

export function Page() {
    return (
        <ClientWrapper>
            <ServerContent />  {/* Server Component 通过 children 传递 */}
        </ClientWrapper>
    );
}

4. 数据获取模式

4.1 Server Component 直接获取

jsx
// ✅ 推荐:数据获取在 Server Component
async function ProductPage({ id }) {
    // 直接在组件中获取数据
    const product = await db.products.findById(id);
    const reviews = await db.reviews.findByProductId(id);
    
    return (
        <div>
            <ProductInfo product={product} />
            <ReviewList reviews={reviews} />
            <AddReviewForm productId={id} />  {/* Client Component */}
        </div>
    );
}

4.2 并行数据获取

jsx
async function Dashboard() {
    // 并行获取,不阻塞
    const [user, posts, notifications] = await Promise.all([
        getUser(),
        getPosts(),
        getNotifications()
    ]);
    
    return (
        <div>
            <UserProfile user={user} />
            <PostList posts={posts} />
            <NotificationBell notifications={notifications} />
        </div>
    );
}

4.3 结合 Suspense 流式渲染

jsx
import { Suspense } from 'react';

async function SlowComponent() {
    const data = await fetchSlowData();  // 需要 3 秒
    return <div>{data}</div>;
}

export function Page() {
    return (
        <div>
            <h1>Dashboard</h1>
            <FastComponent />  {/* 立即显示 */}
            
            <Suspense fallback={<Skeleton />}>
                <SlowComponent />  {/* 流式传输,3 秒后显示 */}
            </Suspense>
        </div>
    );
}

5. Flight 协议

5.1 什么是 Flight?

Flight 是 React 用于序列化 Server Component 渲染结果的协议。

Server Component 渲染


    Flight 格式 (类似 JSON,但支持更多类型)


    Stream 传输到客户端


    React 客户端解析并渲染

5.2 Flight 数据示例

javascript
// Server Component 渲染结果的 Flight 格式
0:["$","div",null,{"children":[
  ["$","h1",null,{"children":"Hello"}],
  ["$","$L1",null,{}]  // $L1 表示需要加载的 Client Component
]}]
1:I["./ClientComponent.js",["default"]]  // Client Component 的加载指令

5.3 Streaming 流式传输

┌─────────────────────────────────────────────────┐
│              Streaming SSR 流程                  │
├─────────────────────────────────────────────────┤
│                                                 │
│  1. 立即发送 Shell (HTML 框架)                   │
│     <html><body><div id="root">                │
│                                                 │
│  2. 流式发送内容                                 │
│     <script>$RC("B:1", "<div>Content</div>")</script>
│                                                 │
│  3. Suspense 边界完成后发送                      │
│     <script>$RC("B:2", "<div>Slow</div>")</script>
│                                                 │
│  4. 发送 </body></html> 结束                    │
│                                                 │
└─────────────────────────────────────────────────┘

6. 组件设计模式

6.1 最小化 Client Component

jsx
// ❌ 错误:整个组件都是 Client
'use client';
function ProductPage({ id }) {
    const [quantity, setQuantity] = useState(1);
    const product = useProduct(id);  // 客户端获取
    
    return (
        <div>
            <h1>{product.name}</h1>
            <p>{product.description}</p>
            <QuantitySelector value={quantity} onChange={setQuantity} />
        </div>
    );
}

// ✅ 正确:只有交互部分是 Client
// ProductPage.tsx (Server)
async function ProductPage({ id }) {
    const product = await getProduct(id);  // 服务端获取
    
    return (
        <div>
            <h1>{product.name}</h1>
            <p>{product.description}</p>
            <QuantitySelector />  {/* 只有这个是 Client */}
        </div>
    );
}

// QuantitySelector.tsx
'use client';
function QuantitySelector() {
    const [quantity, setQuantity] = useState(1);
    return <input type="number" value={quantity} onChange={...} />;
}

6.2 Props 序列化限制

jsx
// ❌ 不能传递函数给 Client Component
function ServerComponent() {
    const handleClick = () => console.log('clicked');
    return <ClientButton onClick={handleClick} />;  // 错误!
}

// ✅ 使用 Server Actions
function ServerComponent() {
    async function handleClick() {
        'use server';
        console.log('clicked on server');
    }
    return <ClientButton onClick={handleClick} />;  // Server Action 可以
}

// 可序列化的 props:
// - 原始值: string, number, boolean, null, undefined
// - 数组和普通对象 (包含可序列化值)
// - Date, Map, Set (有限支持)
// - Server Actions (特殊处理)
// - React Elements (JSX)

7. 缓存与重验证

7.1 数据缓存

jsx
// Next.js 中的缓存控制
async function getProduct(id) {
    // 默认缓存
    const res = await fetch(`/api/products/${id}`);
    
    // 不缓存
    const res = await fetch(`/api/products/${id}`, { cache: 'no-store' });
    
    // 定时重验证
    const res = await fetch(`/api/products/${id}`, { 
        next: { revalidate: 3600 }  // 1小时
    });
}

7.2 重验证策略

jsx
import { revalidatePath, revalidateTag } from 'next/cache';

// Server Action 中重验证
async function createPost(formData) {
    'use server';
    
    await db.posts.create(...);
    
    // 重验证特定路径
    revalidatePath('/posts');
    
    // 重验证特定标签
    revalidateTag('posts');
}

8. 面试高频问题

Q1: Server Component 和 SSR 的区别?

对比项SSRServer Components
运行时机每次请求每次请求或构建时
输出HTML 字符串React 树 (Flight)
Hydration全量 HydrationSelective Hydration
客户端 JS发送完整组件代码只发送 Client Component
状态Hydration 后有状态永远无状态

Q2: 什么时候用 Server Component vs Client Component?

Server Component

  • 数据获取
  • 访问后端资源(数据库、文件)
  • 敏感信息(API keys)
  • 大型依赖(markdown 解析)

Client Component

  • 交互(onClick, onChange)
  • 状态(useState, useReducer)
  • 生命周期(useEffect)
  • 浏览器 API(localStorage, geolocation)

Q3: 为什么 Client Component 不能 import Server Component?

Server Component 只在服务端运行,客户端没有运行它的能力。如果 Client Component 直接 import,打包时会把 Server Component 代码包含进去,但客户端无法执行(可能包含数据库访问等)。

解决方案:通过 children 或 props 传递 Server Component。

Q4: Server Components 如何处理认证?

jsx
import { cookies } from 'next/headers';

async function AuthenticatedPage() {
    const session = await getSession(cookies());
    
    if (!session) {
        redirect('/login');
    }
    
    const user = await getUser(session.userId);
    return <Dashboard user={user} />;
}

Q5: 如何调试 Server Components?

  1. 服务端日志console.log 输出在服务端终端
  2. React DevTools:显示组件树,标记 Server/Client
  3. Network 面板:查看 Flight 数据流
  4. 错误边界:使用 error.tsx 捕获错误

前端面试知识库