React Server Components

'use client' は、React Server Componentsで使用するためのものです。

'use client' を使用すると、クライアントで実行されるコードをマークできます。


リファレンス

'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' でマークされたファイルが Server Component からインポートされる場合、互換性のあるバンドラーは、モジュールのインポートをサーバー実行コードとクライアント実行コードの境界として扱います。

RichTextEditor の依存関係として、formatDateButton は、それらのモジュールに 'use client' ディレクティブが含まれているかどうかに関係なく、クライアントで評価されます。単一のモジュールは、サーバーコードからインポートされるとサーバーで評価され、クライアントコードからインポートされるとクライアントで評価される可能性があることに注意してください。

注意事項

  • 'use client' は、ファイルの先頭、インポートや他のコードよりも上 (コメントは OK) に記述する必要があります。これらは、シングルクォートまたはダブルクォートで記述する必要がありますが、バックティックは使用できません。
  • 'use client' モジュールが別のクライアントレンダリングされたモジュールからインポートされた場合、ディレクティブは効果がありません。
  • コンポーネントモジュールに 'use client' ディレクティブが含まれている場合、そのコンポーネントのすべての使用は、クライアントコンポーネントであることが保証されます。ただし、コンポーネントは 'use client' ディレクティブを持っていなくても、クライアントで評価される可能性があります。
    • コンポーネントの使用は、'use client' ディレクティブを持つモジュールで定義されている場合、または 'use client' ディレクティブを含むモジュールの推移的な依存関係である場合、クライアントコンポーネントと見なされます。それ以外の場合は、サーバーコンポーネントです。
  • クライアント評価用にマークされたコードは、コンポーネントに限定されません。クライアントモジュールサブツリーの一部であるすべてのコードは、クライアントに送信され、クライアントによって実行されます。
  • サーバー評価されたモジュールが 'use client' モジュールから値をインポートする場合、値は React コンポーネントであるか、クライアントコンポーネントに渡されるサポートされているシリアライズ可能なプロパティ値である必要があります。その他のユースケースでは例外がスローされます。

'use client' はどのようにクライアントコードをマークするのか

Reactアプリでは、コンポーネントはしばしば個別のファイル、つまりモジュールに分割されます。

React Server Componentsを使用するアプリの場合、アプリはデフォルトでサーバーでレンダリングされます。'use 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>
    </>
  );
}

このサンプルアプリのモジュール依存関係ツリーでは、InspirationGenerator.js'use client' ディレクティブは、そのモジュールとすべての推移的な依存関係をクライアントモジュールとしてマークします。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'.
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 はクライアントコンポーネントです。

詳細

なぜ FancyText はサーバーコンポーネントとクライアントコンポーネントの両方なのか?

上記の定義によれば、コンポーネント FancyText はサーバーコンポーネントとクライアントコンポーネントの両方ですが、どうしてそうなるのでしょうか。

まず、「コンポーネント」という用語があまり正確ではないことを明確にしましょう。以下に「コンポーネント」を理解するための2つの方法を示します。

  1. 「コンポーネント」は、コンポーネント定義を指す場合があります。ほとんどの場合、これは関数になります。
// This is a definition of a component
function MyComponent() {
return <p>My Component</p>
}
  1. 「コンポーネント」は、その定義のコンポーネントの使用を指す場合もあります。
import MyComponent from './MyComponent';

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

多くの場合、概念を説明する際に不正確さは重要ではありませんが、この場合は重要です。

サーバーコンポーネントまたはクライアントコンポーネントについて話す場合、コンポーネントの使用を指しています。

  • コンポーネントが 'use client' ディレクティブを持つモジュールで定義されている場合、またはコンポーネントがクライアントコンポーネントでインポートおよび呼び出されている場合、コンポーネントの使用はクライアントコンポーネントです。
  • それ以外の場合、コンポーネントの使用はサーバーコンポーネントです。
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.
レンダリングツリーは、コンポーネントの使用を示しています。

FancyText の質問に戻ると、コンポーネント定義には 'use client' ディレクティブがなく、2つの使用があることがわかります。

FancyTextApp の子としての使用は、その使用をサーバーコンポーネントとしてマークします。FancyTextInspirationGenerator の下でインポートおよび呼び出されると、FancyText のその使用は、InspirationGenerator'use client' ディレクティブが含まれているため、クライアントコンポーネントです。

これは、FancyText のコンポーネント定義がサーバーで評価され、クライアントコンポーネントの使用をレンダリングするためにクライアントによってダウンロードされることも意味します。

詳細

Copyright はクライアントコンポーネントの InspirationGenerator の子としてレンダリングされるため、サーバーコンポーネントであることに驚くかもしれません。

'use 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' は、モジュール依存関係ツリー上のサーバーコードとクライアントコードの境界を定義します。

モジュール依存関係ツリーでは、App.jsCopyright.js モジュールから Copyright をインポートして呼び出していることがわかります。Copyright.js'use client' ディレクティブが含まれていないため、コンポーネントの使用はサーバーでレンダリングされます。App はルートコンポーネントであるため、サーバーでレンダリングされます。

クライアントコンポーネントは、JSXをpropsとして渡せるため、サーバーコンポーネントをレンダリングできます。この場合、InspirationGeneratorCopyrightchildrenとして受け取ります。ただし、InspirationGenerator モジュールは、Copyright モジュールを直接インポートしたり、コンポーネントを呼び出したりすることはありません。これはすべて App によって行われます。実際、Copyright コンポーネントは、InspirationGenerator がレンダリングを開始する前に完全に実行されます。

重要なのは、コンポーネント間の親子関係によるレンダリングが、同じレンダリング環境を保証するものではないということです。

'use client' を使用するタイミング

'use client' を使用すると、コンポーネントがクライアントコンポーネントであるタイミングを決定できます。サーバーコンポーネントがデフォルトであるため、サーバーコンポーネントの利点と制限事項の概要を以下に示します。これにより、クライアントでレンダリングする必要があるものを判断できます。

簡単にするために、ここではサーバーコンポーネントについて説明しますが、同じ原則がサーバーで実行されるアプリ内のすべてのコードに適用されます。

サーバーコンポーネントの利点

  • サーバーコンポーネントは、クライアントによって送信および実行されるコードの量を減らすことができます。クライアントによってバンドルおよび評価されるのは、クライアントモジュールのみです。
  • サーバーコンポーネントは、サーバーで実行されるというメリットがあります。ローカルファイルシステムにアクセスでき、データフェッチやネットワークリクエストのレイテンシが低い場合があります。

サーバーコンポーネントの制限事項

  • サーバーコンポーネントは、イベントハンドラーがクライアントによって登録およびトリガーされる必要があるため、インタラクションをサポートできません。
    • たとえば、onClick のようなイベントハンドラーは、クライアントコンポーネントでのみ定義できます。
  • サーバーコンポーネントは、ほとんどのフックを使用できません。
    • サーバーコンポーネントがレンダリングされるとき、その出力は基本的にクライアントがレンダリングするためのコンポーネントのリストです。サーバーコンポーネントは、レンダリング後にメモリに保持されず、独自のステートを持つことはできません。

サーバーコンポーネントから返されるシリアライズ可能な型

Reactアプリと同様に、親コンポーネントは子コンポーネントにデータを渡します。異なる環境でレンダリングされるため、サーバーコンポーネントからクライアントコンポーネントにデータを渡すには、特別な配慮が必要です。

サーバーコンポーネントからクライアントコンポーネントに渡されるpropsの値は、シリアライズ可能である必要があります。

シリアライズ可能なpropsには以下が含まれます。

特に、これらはサポートされていません

  • クライアントマークされたモジュールからエクスポートされていない、または 'use server' でマークされていない関数
  • クラス
  • 任意のクラスのインスタンスであるオブジェクト (前述の組み込みオブジェクト以外)、またはnullプロトタイプを持つオブジェクト
  • グローバルに登録されていないシンボル。例: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 フックとイベントハンドラーの両方を必要とするため、このコンポーネントはクライアントコンポーネントである必要があり、先頭に 'use client' ディレクティブが必要です。

対照的に、インタラクションなしで UI をレンダリングするコンポーネントは、クライアントコンポーネントである必要はありません。

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 は、サーバー上のローカルファイルシステムから読み込むため、サーバーコンポーネントでのみ可能なため、サーバーコンポーネントである必要があります。

サーバーまたはクライアントのみの機能を使用せず、どこでレンダリングされるかに関係なく使用できるコンポーネントもあります。以前の例では、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 パス文字列を返すコンポーネントは、コンポーネントをクライアントコンポーネントに強制する方が効率的な場合の 1 つです。

クライアント API の使用

React アプリは、Web ストレージ、オーディオおよびビデオの操作、デバイスハードウェアなどのブラウザー 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 パターンまたはロジックを処理するために、サードパーティライブラリを利用することがよくあります。

これらのライブラリは、コンポーネントフックまたはクライアント API に依存する場合があります。次の React API を使用するサードパーティコンポーネントは、クライアントで実行する必要があります

これらのライブラリが React サーバーコンポーネントと互換性があるように更新されている場合は、独自の 'use client' マーカーが既に含まれているため、サーバーコンポーネントから直接使用できます。ライブラリが更新されていない場合、またはクライアントでのみ指定できるイベントハンドラーなどの props がコンポーネントに必要な場合は、サードパーティのクライアントコンポーネントと、それを使用するサーバーコンポーネントの間に、独自のクライアントコンポーネントファイルを追加する必要がある場合があります。