鲁斯前端布鲁斯前端

文章中英模式

布鲁斯前端React面试题目 - Server Components & Hydration

深入解析React Server Components的概念、Hydration原理与使用案例。了解RSC如何改变前端开发模式,提升性能与用户体验,以及与传统客户端渲染的区别。

影片縮圖

懒得看文章?那就来看视频吧

React Server Components是什么?

React Server Components (RSC) 是React团队推出的新功能,让你的React组件可以在服务器上运行,而不是只在浏览器中运行。

简单来说,RSC让你可以决定:「这个组件应该在服务器上跑」或「这个组件应该在浏览器上跑」,从而结合两者的优点。

传统React vs Server Components

传统React

  • 1. 所有组件都在浏览器运行
  • 2. 需要下载所有JS代码
  • 3. 数据获取需要额外API请求

渲染流程

服务器:发送React代码包
浏览器:下载JS代码包
浏览器:执行React代码
浏览器:发送API请求获取数据
浏览器:渲染UI

Server Components

  • 1. 部分组件在服务器运行
  • 2. 只下载需要的JS代码
  • 3. 可直接在服务器获取数据

渲染流程

服务器:执行Server Components
服务器:直接获取数据
服务器:生成HTML/UI描述
服务器:只发送Client Components的JS
浏览器:接收HTML(无JS负担)
浏览器:只处理交互组件

注意: Server Components不等于SSR!SSR是整个应用先在服务器渲染成HTML,再在浏览器「水合」成可交互应用;而RSC可以只在服务器渲染某些组件,减少浏览器负担。

如何使用Server Components

Server Components与Client Components工作原理

架构对比

Server Components

在服务器上渲染

直接访问服务器资源

(数据库、文件系统等)

输出静态HTML和引用

无JavaScript传输到客户端

Client Components

在浏览器中渲染

可使用React Hooks

(useState, useEffect等)

处理用户交互

JavaScript代码传输到客户端

浏览器最终接收到的内容

Server Components:

已渲染的HTML内容 + 引用占位符

Client Components:

JavaScript代码 + 初始状态

最终结果:

RSC Payload (特殊格式的JSON) + JavaScript Bundle (仅Client Components)

实际案例:无交互的排行榜

以下是一个典型的Server Component使用案例 - 纯显示的数据排行榜:

// LeaderboardPage.js (Server Component)
// 注意:没有"use client"指令,默认为Server Component
import { getTopUsers } from '@/lib/database';

// 这个异步组件直接在服务器上获取数据
export default async function LeaderboardPage() {
  // 直接访问数据库,无需API调用
  const topUsers = await getTopUsers(50);
  
  return (
    <div className="leaderboard">
      <h1>本周排行榜</h1>
      
      <div className="leaderboard-list">
        {topUsers.map((user, index) => (
          <div key={user.id} className="leaderboard-item">
            <div className="rank">{index + 1}</div>
            <div className="user-avatar">
              <img src={user.avatar} alt={user.name} />
            </div>
            <div className="user-info">
              <h3>{user.name}</h3>
              <p>{user.points}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

优势说明:

  • 1. 数据直接在服务器获取,无需额外API请求
  • 2. 不需要加载状态处理逻辑,服务器已完成渲染
  • 3. 浏览器不需要下载数据获取和处理的JavaScript代码
  • 4. 由于没有交互功能,整个组件可保持为Server Component
  • 5. SEO友好,搜索引擎可直接爬取完整内容

排行榜渲染流程图解

Server Component vs Client Component 排行榜渲染流程
Server Component
1

用户请求页面

2

服务器执行React组件

直接访问数据库/文件系统

3

服务器渲染HTML

4

返回完整HTML

单次网络请求

5

浏览器立即显示内容

无需等待额外请求

Client Component
1

用户请求页面

2

服务器返回基本HTML和JS

需下载大量JS代码

3

浏览器下载并执行JS

消耗客户端资源

4

组件发送API请求获取数据

额外网络请求延迟

5

获取数据后渲染UI

需处理加载和错误状态

两种模式对比

Server Component 优势

  • 1. 减少网络请求次数
  • 2. 减少客户端JS体积
  • 3. 无需处理加载状态
  • 4. 直接访问后端资源
  • 5. SEO友好
  • 6. 首次内容显示更快

Client Component 劣势

  • 1. 需要额外的API请求
  • 2. 较大的JS包体积
  • 3. 需要处理加载状态
  • 4. 需要额外的错误处理
  • 5. 首次内容显示较慢
  • 6. SEO不友好

使用React Server Components的主要优势

1. 减少JavaScript下载量

服务器组件的代码不会发送到浏览器,只发送渲染结果,大幅减少用户需要下载的JS量。

例如:

使用大型日期处理库的组件,在服务器渲染后,浏览器不需要下载该库

2. 直接访问服务器资源

可以直接在组件中访问数据库或文件系统,无需额外API。

// 服务器组件可直接查询数据库
async function ProductList() {
  // 直接从数据库获取数据
  const products = await db.query('SELECT * FROM products');
  
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

3. 更快的页面加载

减少JS下载量并消除API请求,让页面加载和交互速度更快。

4. 保留客户端状态

更新服务器组件时,不会重置客户端组件的状态,使体验更流畅。

Server Components vs Client Components

特性Server ComponentsClient Components
渲染位置服务器客户端浏览器
能否使用useState/useEffect❌ 不能✅ 能
能否访问服务器资源✅ 能❌ 不能
事件处理❌ 不支持✅ 支持
对包大小的影响不增加客户端包大小增加客户端包大小
文件命名(Next.js)预设格式"use client" 指令

同时使用 Server Components 和 Client Components (Next.js 13+)

「叶子」模式

将交互组件(Client Components)尽可能推到叶子位置,让外层容器保持为Server Components。

// ❌ 不佳的做法
"use client"; // 整个页面都是Client Component
export default function Page() {
  return (
    <Layout>
      <Header />
      <Main />
      <Footer />
    </Layout>
  );
}

// ✅ 优化的做法
// page.js (默认Server Component)
export default function Page() {
  return (
    <Layout>
      <Header />
      <Main>
        <InteractiveWidget /> {/* Client Component只在需要的地方 */}
      </Main>
      <Footer />
    </Layout>
  );
}

数据获取与渲染分离

在Server Components中获取数据,然后将数据作为props传递给Client Components。

// Server Component负责数据获取
async function ProductPage({ productId }) {
  // 在服务器上获取数据
  const product = await fetchProduct(productId);
  const inventory = await fetchInventory(productId);
  
  // 将数据传递给客户端组件进行交互
  return (
    <div>
      <ProductInfo product={product} />
      <AddToCartButton 
        productId={productId} 
        inStock={inventory.inStock} 
      />
    </div>
  );
}

// Client Component处理交互
"use client";
function AddToCartButton({ productId, inStock }) {
  // 客户端状态和事件处理
  const [isAdding, setIsAdding] = useState(false);
  
  if (!inStock) {
    return <button disabled>缺货中</button>;
  }
  
  return (
    <button 
      onClick={async () => {
        setIsAdding(true);
        await addToCart(productId);
        setIsAdding(false);
      }}
    >
      {isAdding ? '添加中...' : '加入购物车'}
    </button>
  );
}

深入理解 React 水合 (Hydration) 过程

水合是React将静态HTML「注入生命」的关键过程,将静态页面转换为动态、可交互的应用程序。

🔄 完整的水合过程:

1. 服务器端渲染

产生初始HTML和数据

2. HTML传送到浏览器

用户看到静态内容

3. JS加载与解析

React代码开始执行

4. 事件监听器附加

绑定所有互动功能

5. 状态初始化

设置初始React状态

6. 完全互动状态

应用程序完全可用

⚠️ 水合过程中的常见问题

1. 内容不匹配问题

// 🚫 问题示例:服务器和客户端渲染不一致
function UserGreeting() {
  // 在服务器和客户端产生不同的问候语
  const greeting = new Date().getHours() < 12 ? '早安' : '午安';
  return <h1>{greeting}</h1>;
}

// ✅ 解决方案:确保一致性
function UserGreeting() {
  const [greeting, setGreeting] = useState('您好'); // 预设值

  useEffect(() => {
    // 只在客户端更新问候语
    const hour = new Date().getHours();
    setGreeting(hour < 12 ? '早安' : '午安');
  }, []);

  return <h1>{greeting}</h1>;
}

2. 水合期间的效能问题

  • 大量JavaScript代码导致水合延迟
  • 过早执行复杂计算影响互动性
  • 未正确处理异步数据加载

🔥 常见面试题目

(一) React Server Components是什么?它与传统SSR有什么不同?

解答: React Server Components (RSC) 让部分组件只在服务器上运行,减轻浏览器负担。

传统SSR

1. 整个应用在服务器渲染

2. 所有JS都发送到浏览器

3. 浏览器需水合整个应用

4. 后续更新全在浏览器端

Server Components

1. 可选择哪些组件在服务器渲染

2. 只发送Client Components的JS

3. 浏览器只水合Client Components

4. 可持续从服务器获取更新

想象SSR像是拍照片发给朋友后,朋友还需下载修图软件;而RSC是只发送修好的照片,你朋友可以直接发上IG。

(二) 何时应该使用Server Components和Client Components?

Server Components 适用于:

  • 纯展示内容,无交互需求、需要直接访问数据库
  • // 直接查询数据库
    
    async function Posts() {
      const posts = await db.getPosts();
      return <PostList posts={posts} />;
    }
  • 使用大型依赖库(如日期、格式化)
  • SEO重要的内容

Client Components 适用于:

  • 需要用户交互的界面
    "use client";
    
    function Counter() {
      const [count, setCount] = useState(0);
      return <button onClick={() => setCount(count + 1)}>{count}</button>;
    }
  • 使用React Hooks (useState等)
  • 需要浏览器API (localStorage等)
  • 需要即时反馈的表单