Chuyển đến nội dung chính

第 9 課:伺服器元件和客戶端元件

React 伺服器元件 (RSC),「使用客戶端」指令。伺服器與客戶端元件的邊界。串流媒體、懸念、載入狀態。構圖模式。

💻 程式設計 — 第 9 課 第 9 課:伺服器元件和客戶端 組件

React 和 Next.js:從基礎到高級

第 3 部分:Next.js 基礎知識

亞洲開發網

1. 伺服器元件與客戶端元件

特點伺服器組件(預設)客戶端元件(“使用客戶端”)
渲染僅伺服器伺服器(SSR)+客戶端(水合)
JavaScript 套件0 KB(未發送 JS)包含在捆綁包中
數據獲取直接資料庫/API 訪問useEffect、TanStack 查詢
狀態/效果❌ 沒有 useState/useEffect✅ 全掛鉤支持
事件處理程序❌ 沒有 onClick、onChange✅ 全面的活動支持
瀏覽器API❌沒有視窗、文件✅ 完全瀏覽器訪問
秘密/環境✅ 使用安全❌ 暴露給客戶

2.什麼時候用什麼?

Server Component (default):     Client Component ("use client"):
✅ Static content               ✅ useState, useEffect
✅ Data fetching                 ✅ onClick, onChange
✅ Access database/API           ✅ Browser APIs
✅ Sensitive logic               ✅ Custom hooks with state
✅ Large dependencies            ✅ Third-party libs (charts, editors)

3. 構圖模式

// ✅ Pattern 1: Server Component wraps Client Component
// app/dashboard/page.tsx (Server Component)
import { InteractiveChart } from './InteractiveChart';

export default async function Dashboard() {
  const data = await fetchAnalytics(); // Server-side data fetching

  return (
    <div>
      <h1>Dashboard</h1>
      <InteractiveChart data={data} /> {/* Pass serializable data */}
    </div>
  );
}

// InteractiveChart.tsx
'use client';
export function InteractiveChart({ data }: { data: ChartData[] }) {
  const [filter, setFilter] = useState('all');
  // Interactive client-side logic
  return <div>{/* chart */}</div>;
}
// ✅ Pattern 2: Children pattern (Server inside Client)
// ClientLayout.tsx
'use client';
export function ClientLayout({ children }: { children: React.ReactNode }) {
  const [sidebarOpen, setSidebarOpen] = useState(true);

  return (
    <div className="flex">
      <Sidebar open={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
      <main>{children}</main> {/* children can be Server Components! */}
    </div>
  );
}

// page.tsx (Server Component)
import { ClientLayout } from './ClientLayout';

export default async function Page() {
  const data = await fetchData(); // Server-side

  return (
    <ClientLayout>
      <ServerContent data={data} /> {/* Server component as children */}
    </ClientLayout>
  );
}
// ❌ Anti-pattern: Importing Server Component INTO Client Component
'use client';
import ServerComponent from './ServerComponent'; // ⚠️ Becomes client component!

// ✅ Fix: Pass as children or props

4. 直播與懸疑

import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Mỗi Suspense boundary stream independently */}
      <Suspense fallback={<CardSkeleton />}>
        <RevenueChart /> {/* Async server component */}
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders /> {/* Async server component */}
      </Suspense>

      <Suspense fallback={<ListSkeleton />}>
        <TopProducts /> {/* Async server component */}
      </Suspense>
    </div>
  );
}

// Async Server Component
async function RevenueChart() {
  const data = await fetchRevenue(); // Can take 2-3 seconds
  return <Chart data={data} />;
}

async function RecentOrders() {
  const orders = await fetchRecentOrders(); // Independent fetch
  return <OrderTable orders={orders} />;
}

5. 序列化規則

// Server → Client props must be serializable:
// ✅ string, number, boolean, null, undefined
// ✅ Array, plain Object
// ✅ Date (serialized as string)
// ❌ Functions (cannot pass onClick from server)
// ❌ Class instances
// ❌ Symbols
// ❌ DOM nodes

// ✅ OK
<ClientComponent data={jsonData} count={42} />

// ❌ Error
<ClientComponent onClick={() => {}} ref={myRef} />

下一篇: 數據獲取和緩存 — 伺服器端取得、ISR、重新驗證和快取策略。