魯斯前端布魯斯前端

文章中英模式

布魯斯前端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等)
  • 需要即時反饋的表單