'使用客戶端' - This feature is available in the latest Canary

金絲雀

'使用客戶端' 僅在您使用 React Server Components 或建立與它們相容的函式庫時需要。

'使用客戶端' 讓您可以標記在客戶端上執行的程式碼。


參考

'use client'

在檔案最上方加入 'use client' 以標示模組及其傳遞相依模組為用戶端程式碼。

'use client';

import { useState } from 'react';
import { formatDate } from './formatters';
import Button from './button';

export default function RichTextEditor({ timestamp, text }) {
const date = formatDate(timestamp);
// ...
const editButton = <Button />;
// ...
}

當一個標記為 'use client' 的檔案從伺服器元件匯入時,相容的打包器會將模組匯入視為伺服器執行和用戶端執行程式碼之間的界線。

作為 RichTextEditor 的相依項,formatDateButton 也會在用戶端評估,而不管其模組是否包含 'use client' 指令。請注意,當從伺服器程式碼匯入時,單一模組可能會在伺服器上評估,而從用戶端程式碼匯入時則會在用戶端上評估。

注意事項

  • 'use client' 必須置於檔案的最開頭,在任何匯入或其他程式碼(註解是可以的)之前。它們必須使用單引號或雙引號撰寫,但不能使用反引號。
  • 'use client' 模組從其他由用戶端呈現的模組匯入時,指令不會產生任何效果。
  • 當元件模組包含 'use client' 指令時,保證該元件的任何使用都是用戶端元件。但是,即使元件沒有 'use client' 指令,它仍然可以在用戶端評估。
    • 如果元件使用是在具有 'use client' 指令的模組中定義,或者它是包含 'use client' 指令的模組的遞移相依項,則該元件使用會被視為用戶端元件。否則,它就是伺服器元件。
  • 標示為用戶端評估的程式碼不限於元件。用戶端模組子樹中所有程式碼都會傳送給用戶端並由用戶端執行。
  • 當伺服器評估模組從 'use client' 模組匯入值時,這些值必須是 React 元件或 支援的序列化 prop 值,才能傳遞給 Client 元件。任何其他使用案例都會擲回例外狀況。

'use client' 如何標記 Client 程式碼

在 React 應用程式中,元件通常會拆分為不同的檔案,或 模組

對於使用 React Server Components 的應用程式,應用程式預設會由伺服器呈現。'use client'模組相依樹 中引入伺服器-Client 界線,實際上會建立 Client 模組的子樹。

為了更清楚說明這一點,請考慮以下 React Server Components 應用程式。

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

在此範例應用程式的模組相依性樹中,'use client' 指令在 InspirationGenerator.js 中標記該模組及其所有傳遞相依性為 Client 模組。從 InspirationGenerator.js 開始的子樹現在標記為 Client 模組。

A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.

'use client' 將 React Server Components 應用程式的模組相依性樹區隔開來,將 InspirationGenerator.js 及其所有相依性標記為由用戶端呈現。

在呈現期間,架構會伺服器呈現根元件,並繼續透過 呈現樹,選擇退出評估從用戶端標記程式碼匯入的任何程式碼。

接著將呈現樹的伺服器呈現部分傳送到用戶端。用戶端在下載其用戶端程式碼後,接著完成呈現樹的其餘部分。

A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.

React Server Components 應用程式的呈現樹。 InspirationGenerator 及其子元件 FancyText 是從用戶端標記程式碼匯出的元件,並視為用戶端元件。

我們提出下列定義

  • 用戶端元件 是呈現樹中在用戶端呈現的元件。
  • 伺服器元件 是呈現樹中在伺服器呈現的元件。

在範例應用程式中,AppFancyTextCopyright 都是伺服器渲染的,並視為伺服器元件。由於 InspirationGenerator.js 及其傳遞相依項標記為客戶端程式碼,因此元件 InspirationGenerator 及其子元件 FancyText 是客戶端元件。

Deep Dive

How is FancyText both a Server and a Client Component?

By the above definitions, the component FancyText is both a Server and Client Component, how can that be?

First, let’s clarify that the term “component” is not very precise. Here are just two ways “component” can be understood:

  1. A “component” can refer to a component definition. In most cases this will be a function.
// This is a definition of a component
function MyComponent() {
return <p>My Component</p>
}
  1. A “component” can also refer to a component usage of its definition.
import MyComponent from './MyComponent';

function App() {
// This is a usage of a component
return <MyComponent />;
}

Often, the imprecision is not important when explaining concepts, but in this case it is.

When we talk about Server or Client Components, we are referring to component usages.

  • If the component is defined in a module with a 'use client' directive, or the component is imported and called in a Client Component, then the component usage is a Client Component.
  • Otherwise, the component usage is a Server Component.
A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
A tree graph where each node represents a component and its children as child components. The top-level node is labelled 'App' and it has two child components 'InspirationGenerator' and 'FancyText'. 'InspirationGenerator' has two child components, 'FancyText' and 'Copyright'. Both 'InspirationGenerator' and its child component 'FancyText' are marked to be client-rendered.
A render tree illustrates component usages.

Back to the question of FancyText, we see that the component definition does not have a 'use client' directive and it has two usages.

The usage of FancyText as a child of App, marks that usage as a Server Component. When FancyText is imported and called under InspirationGenerator, that usage of FancyText is a Client Component as InspirationGenerator contains a 'use client' directive.

This means that the component definition for FancyText will both be evaluated on the server and also downloaded by the client to render its Client Component usage.

Deep Dive

Because Copyright is rendered as a child of the Client Component InspirationGenerator, you might be surprised that it is a Server Component.

Recall that 'use client' defines the boundary between server and client code on the module dependency tree, not the render tree.

A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.
A tree graph with the top node representing the module 'App.js'. 'App.js' has three children: 'Copyright.js', 'FancyText.js', and 'InspirationGenerator.js'. 'InspirationGenerator.js' has two children: 'FancyText.js' and 'inspirations.js'. The nodes under and including 'InspirationGenerator.js' have a yellow background color to signify that this sub-graph is client-rendered due to the 'use client' directive in 'InspirationGenerator.js'.

'use client' defines the boundary between server and client code on the module dependency tree.

In the module dependency tree, we see that App.js imports and calls Copyright from the Copyright.js module. As Copyright.js does not contain a 'use client' directive, the component usage is rendered on the server. App is rendered on the server as it is the root component.

Client Components can render Server Components because you can pass JSX as props. In this case, InspirationGenerator receives Copyright as children. However, the InspirationGenerator module never directly imports the Copyright module nor calls the component, all of that is done by App. In fact, the Copyright component is fully executed before InspirationGenerator starts rendering.

The takeaway is that a parent-child render relationship between components does not guarantee the same render environment.

何時使用 'use client'

使用 'use client',您可以判斷元件是否為 Client 元件。由於 Server 元件為預設值,因此以下簡要說明 Server 元件的優點和限制,以協助您判斷何時需要將某些內容標記為 Client 呈現。

為求簡潔,我們討論 Server 元件,但相同原則適用於應用程式中所有伺服器執行的程式碼。

Server 元件的優點

  • Server 元件可以減少客戶端傳送和執行的程式碼數量。只有 Client 模組會由客戶端套件化並評估。
  • Server 元件受益於在伺服器上執行。它們可以存取本機檔案系統,並且在資料擷取和網路要求方面可能會遇到低延遲。

Server 元件的限制

  • 伺服器元件無法支援互動,因為事件處理常式必須由客戶端註冊和觸發。
    • 例如,如 onClick 等事件處理常式只能在客戶端元件中定義。
  • 伺服器元件無法使用大多數 Hook。
    • 當伺服器元件被渲染時,其輸出基本上是供客戶端渲染的元件清單。伺服器元件在渲染後不會保留在記憶體中,且無法擁有自己的狀態。

伺服器元件傳回的可序列化類型

如同在任何 React 應用程式中,父元件會將資料傳遞給子元件。由於它們是在不同的環境中渲染,因此從伺服器元件傳遞資料到客戶端元件需要額外的考量。

從伺服器元件傳遞到客戶端元件的 Prop 值必須可序列化。

可序列化的 Prop 包括

特別注意,下列項目不受支援

  • 函式,如果未從標記為用戶端的模組匯出,或未標記 'use server'
  • 類別
  • 任何類別的實例物件 (除了上述內建類別) 或具備 空原型 的物件
  • 未在全域註冊的符號,例如 Symbol('my new symbol')

用法

建立互動性和狀態

'use client';

import { useState } from 'react';

export default function Counter({initialValue = 0}) {
  const [countValue, setCountValue] = useState(initialValue);
  const increment = () => setCountValue(countValue + 1);
  const decrement = () => setCountValue(countValue - 1);
  return (
    <>
      <h2>Count Value: {countValue}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </>
  );
}

由於 Counter 同時需要 useState Hook 和事件處理常式來遞增或遞減值,因此此元件必須是 Client 元件,而且需要在最上方有一個 'use client' 指令。

相對地,一個呈現 UI 但沒有互動的元件就不需要是 Client 元件。

import { readFile } from 'node:fs/promises';
import Counter from './Counter';

export default async function CounterContainer() {
const initialValue = await readFile('/path/to/counter_value');
return <Counter initialValue={initialValue} />
}

例如,Counter 的父元件 CounterContainer 不需要 'use client',因為它沒有互動性,也沒有使用狀態。此外,CounterContainer 必須是 Server 元件,因為它會從伺服器上的本機檔案系統讀取,而這只有在 Server 元件中才有可能。

還有不使用任何伺服器或僅限用戶端功能,且在呈現位置上可以不分彼此的元件。在我們較早的範例中,FancyText 就是這樣的元件。

export default function FancyText({title, text}) {
return title
? <h1 className='fancy title'>{text}</h1>
: <h3 className='fancy cursive'>{text}</h3>
}

在這種情況下,我們不加入 'use client' 指令,導致 FancyText輸出(而非其原始碼)在從伺服器元件參照時會傳送至瀏覽器。如同在較早的 Inspirations 應用程式範例中所示,FancyText 會同時用作伺服器或用戶端元件,視其匯入和使用的位置而定。

但如果 FancyText 的 HTML 輸出相對於其原始碼(包括相依性)較大,則強制它永遠成為用戶端元件可能會更有效率。會傳回長 SVG 路徑字串的元件就是一個可能強制元件成為用戶端元件會更有效率的案例。

使用客戶端 API

您的 React 應用程式可以使用客戶端特定的 API,例如瀏覽器的網路儲存、音訊和影片處理以及裝置硬體的 API,以及 其他 API。

在此範例中,組件使用 DOM API 來處理 canvas 元素。由於這些 API 僅在瀏覽器中可用,因此必須將其標示為客戶端組件。

'use client';

import {useRef, useEffect} from 'react';

export default function Circle() {
const ref = useRef(null);
useLayoutEffect(() => {
const canvas = ref.current;
const context = canvas.getContext('2d');
context.reset();
context.beginPath();
context.arc(100, 75, 50, 0, 2 * Math.PI);
context.stroke();
});
return <canvas ref={ref} />;
}

使用第三方程式庫

在 React 應用程式中,您通常會利用第三方程式庫來處理常見的 UI 模式或邏輯。

這些程式庫可能依賴元件 Hooks 或用戶端 API。使用以下任何 React API 的第三方元件必須在用戶端執行

如果這些函式庫已更新為與 React Server Components 相容,則它們將包含自己的 'use client' 標記,讓你可以直接從 Server Components 中使用它們。如果函式庫尚未更新,或如果元件需要只能在客戶端指定的屬性(例如事件處理常式),你可能需要在第三方 Client Component 和你想要使用它的 Server Component 之間新增自己的 Client Component 檔案。