Featured image of post React 伺服器元件:Web 渲染的未來Featured image of post React 伺服器元件:Web 渲染的未來

React 伺服器元件:Web 渲染的未來

探索 React Server Components 架構、伺服器與用戶端邊界、串流 SSR、資料擷取模式、Suspense 整合以及現有專案的採用策略。

React 服务器组件 (RSC) 代表了 React 应用程序渲染方式的范式转变。组件专门在服务器上运行,向客户端发送零 JavaScript。這與伺服器端渲染(SSR)不同,後者在伺服器上渲染元件,但仍然將它們的所有 JavaScript 發送到客戶端進行補水。 RSC 產生一種特殊的序列化格式——RSC 有效負載——客戶端上的 React 協調器使​​用它來重建元件樹,而不執行元件程式碼。

核心好處是客戶端 JavaScript 的顯著減少。不需要互動性的佈局元件、資料取得邏輯和表示元素可以完全在伺服器上執行。更小的捆綁包意味著更快的頁面加載、更低的記憶體使用量以及改進的 Core Web Vitals。

伺服器與客戶端的邊界

指令 'use client''use server' 定義邊界。在支援 RSC 的框架(Next.js App Router、Hydrogen 等)中,任何沒有指令的元件預設都是伺服器元件。

// This is a server component by default
import { db } from "@/lib/db";

export default async function ProductList() {
  const products = await db.query("SELECT * FROM products");
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}
// This is a client component
"use client";

import { useState } from "react";

export default function LikeButton({ productId }) {
  const [liked, setLiked] = useState(false);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "♥" : "♡"}
    </button>
  );
}

關鍵規則:伺服器元件不能使用狀態、效果或瀏覽器 API。從伺服器傳遞到客戶端的道具必須是可序列化的-沒有函數,沒有類別實例,沒有循環引用。客戶端元件可以透過組合匯入伺服器元件(將伺服器元件作為子元件或 props 傳遞),但不能直接匯入它們。


串流媒體 SSR 和懸念

RSC 可以開箱即用地進行串流。當頁面使用 Suspense 邊界時,每個邊界可以在其資料可用時獨立進行串流傳輸,而無需等待整個頁面呈現。

export default function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<ProductSkeleton />}>
        <ProductList />
      </Suspense>
      <Suspense fallback={<ReviewSkeleton />}>
        <Reviews />
      </Suspense>
    </div>
  );
}

在此範例中, Header 立即呈現, ProductList 在其資料庫查詢完成時呈現, Reviews 在其查詢完成時呈現。瀏覽器會逐步顯示內容,而不是顯示空白頁面,直到一切準備就緒。這改進了首字節時間 (TTFB) 和最大內容繪製 (LCP)。

公制傳統SSR串流媒體 RSC
TTFB整頁渲染後第一個懸念邊界之後
液晶聚合物補水後第一個流式傳輸塊後
JS 捆綁完整的應用程式包僅限客戶端元件
互動時間補水滿後頁面互動更快

資料取得模式

RSC 最具變革性的功能是直接在元件中取得數據,而無需 API 層。資料庫查詢與使用它們的元件位於同一位置,從而消除了客戶端資料擷取中常見的瀑布問題。

async function ProductDetails({ id }) {
  const product = await db.query(
    "SELECT * FROM products WHERE id = $1",
    [id]
  );

  const reviews = await db.query(
    "SELECT * FROM reviews WHERE product_id = $1",
    [id]
  );

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ReviewList reviews={reviews} />
    </div>
  );
}

React 在渲染期間自動刪除使用 fetch() 發出的重複請求。 React.cache() 函數將此重複資料刪除功能擴展到任何非同步操作,包括資料庫查詢。對於重新驗證,RSC 使用基於時間 (ISR)、按需 (webhooks) 和突變觸發的策略。


伺服器操作的突變

使用 'use server' 指令定義的伺服器操作可以處理表單提交和資料突變,而無需建立 API 路由。

async function createProduct(formData) {
  "use server";

  const name = formData.get("name");
  const price = formData.get("price");

  await db.query(
    "INSERT INTO products (name, price) VALUES ($1, $2)",
    [name, price]
  );

  revalidatePath("/products");
}

export default function ProductForm() {
  return (
    <form action={createProduct}>
      <input name="name" required />
      <input name="price" type="number" required />
      <button type="submit">Create</button>
    </form>
  );
}

伺服器操作支援漸進式增強 - 此表單無需 JavaScript 即可運作。當 JavaScript 可用時,操作會透過 fetch 提交,伺服器僅重新呈現受影響的元件。錯誤處理和樂觀更新是透過客戶端鉤子(例如 React 19 中的 useActionState )進行管理的。


採用策略

對於現有的 Next.js 項目,從 Pages Router 到 App Router 的遷移路徑是增量的。您可以在遷移期間並排運行兩個路由器:

  1. 從資料元件開始:先將靜態或資料量大的頁面移至 RSC
  2. 識別客戶端邊界:任何使用狀態、效果或事件處理程序的元件都會變成 'use client'
  3. 提取交互島:保持客戶端元件較小且處於葉級
  4. 上移資料取得:將資料取得推送到父伺服器元件,將結果向下傳遞

並非所有應用程式都能受益於 RSC。高度互動的儀表板、即時協作工具以及大多數組件使用狀態和效果的應用程式可能獲得的好處微乎其微。在這些情況下,客戶端渲染或傳統 SSR 可能更合適。


效能和捆綁影響

RSC 最可衡量的影響是 JavaScript 捆綁包的減少。先前作為 JavaScript 發送到客戶端的佈局元件、文字較多的頁面和資料獲取邏輯現在僅在伺服器上執行。 RSC 有效負載格式是一種緊湊的二進位表示形式,客戶端上的 React 協調器可以有效地處理它。

網路標籤分析將 RSC 回應顯示為特殊 RSC 內容類型的串流傳輸區塊。每個區塊對應於一個正在解決的 Suspense 邊界。這種流粒度意味著瀏覽器增量地接收和呈現內容,從而提高感知效能,超出了 JavaScript 捆綁測量單獨建議的範圍。

現況與展望

截至 2024 年,React Server Components 已堅定地進入生產領域。 Next.js 14 以 RSC 為核心穩定了 App Router。 React 團隊持續完善模型,致力於開發與 RSC 相容的狀態管理解決方案和改進的工具。 Hydrogen (Shopify) 和 RedwoodJS 等新框架已採用 RSC 作為其主要渲染模型。

持續的爭論集中在複雜性上——RSC 引入了一種新的思維模型,需要時間來學習,並且伺服器/客戶端邊界需要仔細的架構決策。然而,對於內容豐富的網站、電子商務平台和頁面載入效能至關重要的應用程式來說,減少 JavaScript 和改進串流傳輸的好處遠遠超過了學習曲線。