伺服器與客戶端元件

要理解伺服器元件 (Server Components) 與客戶端元件 (Client Components) 的運作方式,首先需要熟悉兩個基礎的網頁概念:

  • 應用程式碼執行的環境:伺服器與客戶端。
  • 區隔伺服器與客戶端程式碼的網路邊界

伺服器與客戶端環境

在網頁應用的情境中:

圖表左側為瀏覽器,右側為伺服器,中間以網路邊界分隔。
  • 客戶端指的是使用者裝置上的瀏覽器,它會向伺服器發送請求以獲取應用程式碼,並將伺服器的回應轉換為使用者可互動的介面。
  • 伺服器指的是資料中心中的電腦,它儲存你的應用程式碼、接收客戶端請求、進行運算後回傳適當的回應。

每個環境都有其獨特的能力與限制。例如,將渲染與資料獲取移至伺服器端,可以減少傳送至客戶端的程式碼量,從而提升應用程式效能。但如先前所學,要讓 UI 具有互動性,你需要在客戶端更新 DOM。

因此,為伺服器與客戶端編寫的程式碼並不相同。某些操作(例如資料獲取或管理使用者狀態)更適合在某個特定環境中執行。

網路邊界

網路邊界是一個概念上的界線,用於區隔不同的環境。

在 React 中,你可以自行決定在元件樹的何處放置網路邊界。例如,你可以在伺服器端獲取資料並渲染使用者的貼文(使用伺服器元件),然後在客戶端為每篇貼文渲染互動式的 LikeButton(使用客戶端元件)。

同樣地,你可以建立一個在伺服器端渲染並在多個頁面間共享的 Nav 元件,但如果你想顯示連結的啟用狀態,則可以在客戶端渲染 Links 列表。

元件樹展示了一個佈局,包含三個子元件:Nav、Page 和 Footer。Page 元件有兩個子元件:Posts 和 LikeButton。Posts 元件在伺服器端渲染,LikeButton 元件在客戶端渲染。

在底層,元件會被拆分為兩個模組圖 (module graph)。**伺服器模組圖(或樹)包含所有在伺服器端渲染的伺服器元件,而客戶端模組圖(或樹)**則包含所有客戶端元件。

當伺服器元件渲染完成後,會以一種稱為 React 伺服器元件負載 (RSC) 的特殊資料格式傳送至客戶端。RSC 負載包含:

  1. 伺服器元件的渲染結果。
  2. 客戶端元件應渲染位置的佔位符(或「洞」)以及對其 JavaScript 檔案的引用。

React 會利用這些資訊來整合伺服器與客戶端元件,並在客戶端更新 DOM。

讓我們看看這實際上是如何運作的。

使用客戶端元件

如前一章所學,Next.js 預設使用伺服器元件——這是為了提升應用程式效能,意味著你無需額外步驟即可採用它們。

回顧瀏覽器中的錯誤訊息,Next.js 警告你嘗試在伺服器元件中使用 useState。你可以將互動式的「讚」按鈕移至客戶端元件來解決此問題。

app 資料夾中建立一個名為 like-button.js 的新檔案,並導出 LikeButton 元件:

/app/like-button.js
export default function LikeButton() {}

<button> 元素和 handleClick() 函式從 page.js 移至新的 LikeButton 元件:

/app/like-button.js
export default function LikeButton() {
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

接著,移動 likes 狀態和 import 語句:

/app/like-button.js
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

現在,要將 LikeButton 設為客戶端元件,請在檔案頂部添加 React 的 'use client' 指令。這會告訴 React 在客戶端渲染此元件。

/app/like-button.js
'use client';
 
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

回到 page.js 檔案,將 LikeButton 元件導入你的頁面:

/app/page.js
import LikeButton from './like-button';
 
function Header({ title }) {
  return <h1>{title ? title : 'Default title'}</h1>;
}
 
export default function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
 
  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <LikeButton />
    </div>
  );
}

儲存兩個檔案並在瀏覽器中查看你的應用。現在錯誤已解決,當你進行更改並儲存後,應該會注意到瀏覽器會自動更新以反映變更。

此功能稱為快速刷新 (Fast Refresh),它能即時反饋你所做的任何編輯,並且已預先配置在 Next.js 中。

總結

總結一下,你學到了伺服器與客戶端環境以及各自的使用時機。你也了解到 Next.js 預設使用 React 伺服器元件以提升效能,以及如何選擇性地使用客戶端元件來讓 UI 的特定部分具有互動性。

延伸閱讀

關於伺服器與客戶端元件還有更多內容可以學習。以下是一些額外資源:

On this page