'use client'
'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'
でマークされているファイルがサーバコンポーネントからインポートされた場合、互換性のあるバンドラは当該モジュールのインポートを、サーバで実行されるコードとクライアントで実行されるコードの境界として扱います。
上記では formatDate
と Button
は RichTextEditor
が依存するモジュールですので、これらのモジュール自体に 'use client'
ディレクティブが含まれているかどうかに関わらず、これらもクライアントで評価されます。ある単一のモジュールが、サーバコードからインポートされた場合はサーバで、クライアントコードからインポートされた場合はクライアントで評価される場合があることに注意してください。
注意点
'use client'
はファイルの冒頭、すなわちインポートや他のコードより先になければなりません(コメントは OK です)。シングルクォートまたはダブルクォートで書かれていなければならず、バックティックは無効です。'use client'
モジュールが別のクライアントレンダーされるモジュールからインポートされた場合、ディレクティブの効果はありません。- コンポーネントモジュールに
'use client'
ディレクティブが含まれている場合、そのコンポーネントは必ずクライアントコンポーネントであることが保証されます。しかしコンポーネントに直接'use client'
ディレクティブがなくとも、クライアントで評価されることがあります。- コンポーネントがクライアントコンポーネントと見なされるのは、それが
'use client'
ディレクティブを含むモジュールで定義されている場合、またはそれが'use client'
ディレクティブを含むモジュールの間接的な依存モジュールである場合です。それ以外の場合、サーバコンポーネントとなります。
- コンポーネントがクライアントコンポーネントと見なされるのは、それが
- クライアントで評価されるようマークされるコードとはコンポーネントに限りません。クライアントモジュールのサブツリーに含まれるすべてのコードは、クライアントに送信され、クライアントで実行されます。
- サーバで評価されるモジュールが
'use client'
のモジュールから値をインポートする場合、その値は React コンポーネントであるか、またはクライアントコンポーネントに渡せるようサポート済のシリアライズ可能な props の型のいずれかでなければなりません。それ以外の方法で使用すると例外がスローされます。
'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
から始まるサブツリー全体がクライアントモジュールとなるのです。
レンダー中、フレームワークはルートコンポーネントから始め、レンダーツリーを順にレンダーしていきますが、その際にクライアントとマークされたものからインポートされたコードの評価を選択的に除外します。
その後レンダーツリーのうちサーバでレンダーされた部分が、クライアントに送信されます。クライアントは、ダウンロードしたクライアントコードを用いて、ツリーの残りの部分のレンダーを完了します。
ここで以下の定義を導入しましょう。
- クライアントコンポーネントとは、レンダーツリーの中の、クライアントでレンダーされるコンポーネントです。
- サーバコンポーネントとは、レンダーツリーの中の、サーバでレンダーされるコンポーネントです。
上記のサンプルアプリでは、App
、FancyText
、Copyright
はすべてサーバでレンダーされるのでサーバコンポーネントと見なされます。InspirationGenerator.js
とその間接的な依存モジュールはクライアントコードとしてマークされているため、コンポーネント InspirationGenerator
とその子コンポーネントである FancyText
はクライアントコンポーネントとなります。
さらに深く知る
上記の定義によれば、コンポーネント FancyText
は、サーバコンポーネントでもありクライアントコンポーネントでもあることになります。どういうことでしょうか?
まずは “コンポーネント” という用語があまり厳密ではないことを明示的に意識しましょう。“コンポーネント” とは以下の 2 つの意味で使用されます。
- 「コンポーネント」はコンポーネントの定義を指すことがあります。ほとんどの場合、これは関数です。
// This is a definition of a component
function MyComponent() {
return <p>My Component</p>
}
- 「コンポーネント」はまた、その定義に従って使用されている個々のコンポーネントを指すこともあります。
import MyComponent from './MyComponent';
function App() {
// This is a usage of a component
return <MyComponent />;
}
ここが厳密でなくとも大抵の場合は概念を説明する際に問題とはなりませんが、この場合はそうではありません。
サーバコンポーネントやクライアントコンポーネントについて話すとき、コンポーネントの個々の使用法を指しています。
- コンポーネントが
'use client'
ディレクティブを含んだモジュールで定義されている場合や、コンポーネントが他のクライアントコンポーネントからインポートされコールされる場合、そのコンポーネントの使用法はクライアントコンポーネントであるということになります。 - それ以外の場合、そのコンポーネントの使用法はサーバコンポーネントだということになります。
FancyText
の問題に戻りましょう。分かるのは、コンポーネントの定義として 'use client'
ディレクティブがないこと、そして使用法として 2 種類あることです。
FancyText
が App
の子として使用されている場合、その使用法はサーバコンポーネントです。FancyText
が InspirationGenerator
の下でインポートされて呼び出されている場合、InspirationGenerator
に 'use client'
ディレクティブが含まれているため、その FancyText
の使用法はクライアントコンポーネントとなります。
つまり、FancyText
のコンポーネント定義がサーバ上で評価される一方で、クライアントコンポーネントとして使用されるためクライアントにダウンロードもされる、ということになります。
さらに深く知る
Copyright
はクライアントコンポーネントである InspirationGenerator
の子としてレンダーされているので、それがサーバコンポーネントになっていることに驚くかもしれません。
'use client'
がサーバとクライアントのコードの境界を、レンダーツリーではなくモジュール依存関係ツリー上で定義することを思い出してください。
モジュール依存関係ツリー上では、App.js
が Copyright.js
モジュールから Copyright
をインポートして呼び出していることがわかります。Copyright.js
には 'use client'
ディレクティブが含まれていないため、このコンポーネントはサーバレンダーで使用されています。App
はルートコンポーネントでありサーバ上でレンダーされるからです。
クライアントコンポーネントがサーバコンポーネントをレンダーできるのは、JSX を props として渡すことができるためです。上記の例の場合、InspirationGenerator
は children として Copyright
を受け取ります。しかし、InspirationGenerator
モジュールは Copyright
モジュールを直接インポートしたり、コンポーネントを呼び出したりしているわけではありません。それらはすべて App
によって行われます。実際、Copyright
コンポーネントは InspirationGenerator
のレンダーすら始まらないうちに完全に実行されます。
つまり、コンポーネント間に親子関係があるからといって、同じ環境でレンダーされることが保証されるわけではない、ということを覚えておきましょう。
'use client'
をいつ使うべきか
'use client'
を使うことで、どのコンポーネントがクライアントコンポーネントであるかを決定できます。デフォルトはサーバコンポーネントですので、その利点と制限事項を簡単に概観することで、何をクライアントレンダーとしてマークする必要があるかを判断できるようにしましょう。
話を簡単にするためサーバコンポーネントについて説明しますが、サーバで実行されるアプリのすべてのコードに同じ原則が適用されます。
サーバコンポーネントの利点
- サーバコンポーネントにより、クライアントに送信され実行されるコードの量を減らすことができます。バンドルされてクライアントで評価されるのはクライアントモジュールだけです。
- サーバコンポーネントにはサーバ上で実行されることに伴う利点があります。ローカルファイルシステムにアクセスでき、データフェッチやネットワークリクエストのレイテンシが低い可能性があります。
サーバコンポーネントの制限事項
- サーバコンポーネントはユーザによるインタラクションをサポートできません。イベントハンドラはクライアントで登録されトリガされる必要があるためです。
- 例えば、
onClick
のようなイベントハンドラはクライアントコンポーネントでのみ定義できます。
- 例えば、
- サーバコンポーネントではほとんどのフックを使用できません。
- サーバコンポーネントがレンダーされるとき、その出力は本質的にクライアントでレンダーすべきコンポーネントのリストになります。サーバコンポーネントはレンダー後にメモリに残らないため、state を持つことはできません。
サーバコンポーネントから返せるシリアライズ可能な型
React アプリケーションでは一般的に、親コンポーネントから子コンポーネントにデータを渡します。これらが異なる環境でレンダーされるため、サーバコンポーネントからクライアントコンポーネントにデータを渡す際には特別な配慮が必要です。
サーバコンポーネントからクライアントコンポーネントに渡される props の値は、シリアライズ可能 (serializable) である必要があります。
シリアライズ可能な props には以下のものがあります:
- プリミティブ
- シリアライズ可能な値を含んだ Iterable
- Date
- プレーンなオブジェクト: オブジェクト初期化子で作成され、シリアライズ可能なプロパティを持つもの
- サーバアクション (server action) としての関数
- クライアントまたはサーバコンポーネントの要素(JSX)
- プロミス
以下のものはサポートされていません。
- クライアントとマークされたモジュールからエクスポートされていない、または
'use server'
でマークされていない関数 - クラス
- 任意のクラスのインスタンス(上記の組み込みクラスを除く)や、null プロトタイプのオブジェクト
- グローバルに登録されていないシンボル、例:
Symbol('my new symbol')
使用法
ユーザ操作や state を実装する
'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
は、インタラクティブではなく state を使用しないため、'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
の(ソースコードではなく)出力結果が、ブラウザに送信されます。先ほどのアプリの例で示したように、FancyText
はどこからインポートされ、どこで使用されるかによって、サーバコンポーネントになることもクライアントコンポーネントになることもあるのです。
しかし、FancyText
の HTML 出力が(依存モジュールも含んだ)ソースコードに比べて大きいような場合は、必ずクライアントコンポーネントとなるように強制する方が効率的かもしれません。一例として、長い SVG パス文字列を返すようなコンポーネントは、強制的にクライアントコンポーネントとしてレンダーする方が効率的である可能性があるでしょう。
クライアント 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 を使用するサードパーティのコンポーネントは、クライアント上で実行する必要があります。
- createContext
react
とreact-dom
のフック、ただしuse
とuseId
は除く- forwardRef
- memo
- startTransition
- DOM の挿入やネイティブプラットフォームビューなどその他のクライアント API
ライブラリがサーバコンポーネントと互換性を有するように更新済みであれば、中に既に 'use client'
マーカが含まれていますので、サーバコンポーネントから直接使用することができます。ライブラリが更新されていない場合、あるいはコンポーネントが受け取る props にクライアントでのみ指定できるイベントハンドラのようなものが含まれている場合、サードパーティのクライアントコンポーネントとそれを使用したいサーバコンポーネントの間に、自分でクライアントコンポーネントファイルを追加する必要があるかもしれません。