緊急避難口
一部のコンポーネントは、Reactの外部にあるシステムを制御し、同期させる必要があるかもしれません。例えば、ブラウザAPIを使用して入力欄にフォーカスを当てたり、Reactを使用せずに実装されたビデオプレーヤーを再生・一時停止したり、リモートサーバーからのメッセージに接続して受信したりする必要があるかもしれません。この章では、Reactの「外部に踏み出し」て外部システムに接続できるようにする緊急避難口について学びます。アプリケーションのロジックとデータフローの大部分は、これらの機能に依存すべきではありません。
refによる値の参照
コンポーネントに何らかの情報を「記憶」させたいが、その情報によって新しいレンダーがトリガーされることを望まない場合、refを使用できます:
stateと同様に、refもReactによって再レンダー間で保持されます。ただし、stateを設定するとコンポーネントが再レンダーされますが、refを変更しても再レンダーは発生しません!そのrefの現在の値には、ref.currentプロパティを通じてアクセスできます。
refは、Reactが追跡しないコンポーネントの秘密のポケットのようなものです。例えば、refを使用してタイムアウトIDやDOM要素など、コンポーネントのレンダリング出力に影響を与えないオブジェクトを格納できます。
refによるDOM操作
Reactはレンダー出力に合わせてDOMを自動的に更新するため、コンポーネントがDOMを操作する必要はあまりありません。ただし、Reactが管理するDOM要素にアクセスする必要がある場合があります。例えば、ノードにフォーカスを当てたり、スクロールさせたり、そのサイズや位置を測定したりするためです。Reactにはこれらを行う組み込みの方法がないため、DOMノードへのrefが必要になります。例えば、以下のボタンをクリックすると、refを使用して入力欄にフォーカスが当たります:
Effectによる同期
一部のコンポーネントは、外部システムと同期する必要があります。例えば、Reactのstateに基づいて非Reactコンポーネントを制御したり、サーバー接続を設定したり、コンポーネントが画面に表示されたときに分析ログを送信したりする場合があります。特定のイベントを処理できるイベントハンドラとは異なり、Effectを使用すると、レンダリング後にコードを実行できます。Effectを使用して、コンポーネントをReact外のシステムと同期させてください。
再生/一時停止を数回押して、ビデオプレーヤーがisPlayingプロップの値とどのように同期し続けるかを確認してください:
多くのEffectは、実行後に「クリーンアップ」を行います。例えば、チャットサーバーへの接続を設定するEffectは、コンポーネントをそのサーバーから切断する方法をReactに伝えるクリーンアップ関数を返すべきです:
開発環境では、ReactはEffectを1回余分に実行して即座にクリーンアップします。これが"✅ Connecting..."が2回表示される理由です。これにより、クリーンアップ関数の実装を忘れないようになります。
Effectが不要な場合もある
EffectはReactのパラダイムからの脱出口です。Reactの外に「踏み出し」、コンポーネントを外部システムと同期させることができます。外部システムが関与していない場合(例えば、propsやstateが変化したときにコンポーネントのstateを更新したい場合)、Effectは不要です。不要なEffectを削除すると、コードは理解しやすくなり、実行が速くなり、エラーも少なくなります。
Effectが不要な一般的なケースは2つあります:
- レンダリングのためのデータ変換にEffectは不要です。
- ユーザーイベントの処理にEffectは不要です。
例えば、他のstateに基づいてstateを調整するためにEffectは不要です:
代わりに、レンダリング中にできるだけ多くの計算を行いましょう:
ただし、外部システムとの同期にはEffectが必要です。
リアクティブEffectのライフサイクル
Effectのライフサイクルはコンポーネントとは異なります。コンポーネントはマウント、更新、アンマウントします。Effectは、何かの同期を開始し、後でその同期を停止するという2つのことしかできません。Effectが時間とともに変化するpropsやstateに依存している場合、このサイクルは複数回発生する可能性があります。
このEffectはroomIdプロップの値に依存しています。プロップはリアクティブな値であり、再レンダリング時に変化する可能性があります。roomIdが変化すると、Effectは再同期(サーバーへの再接続)を行うことに注意してください:
Reactは、Effectの依存関係が正しく指定されているか確認するためのリンタールールを提供しています。上記の例で依存関係のリストにroomIdを指定し忘れた場合、リンターが自動的にそのバグを見つけます。
このトピックを学ぶ準備はできていますか?
Effectのライフサイクルがコンポーネントのライフサイクルとどのように異なるかを学ぶには、リアクティブなEffectのライフサイクルをお読みください。
イベントとEffectを分離する
イベントハンドラは、同じ操作を再度実行したときにのみ再実行されます。イベントハンドラとは異なり、Effectは、読み取るpropsやstateなどの値が前回のレンダー時と異なる場合に再同期します。時には、両方の動作を組み合わせたい場合があります。つまり、一部の値には反応して再実行するが、他の値には反応しないEffectです。
Effect内のすべてのコードはリアクティブです。読み取るリアクティブな値の一部が再レンダーによって変更された場合、再実行されます。例えば、このEffectはroomIdまたはthemeのいずれかが変更された場合にチャットに再接続します:
これは理想的ではありません。roomIdが変更された場合にのみチャットに再接続したいのです。themeを切り替えてもチャットに再接続すべきではありません!themeを読み取るコードをEffectからEffect Eventに移動します:
Effect Event内のコードはリアクティブではないため、themeを変更してもEffectが再接続することはなくなります。
Effectの依存関係を削除する
Effectを記述すると、リンターはEffectが読み取るすべてのリアクティブな値(propsやstateなど)がEffectの依存関係のリストに含まれていることを確認します。これにより、Effectがコンポーネントの最新のpropsやstateと同期し続けることが保証されます。不要な依存関係があると、Effectが頻繁に実行されたり、無限ループが発生したりする可能性があります。それらを削除する方法はケースによって異なります。
例えば、このEffectは、入力を編集するたびに再作成されるoptionsオブジェクトに依存しています:
チャットでメッセージを入力するたびに再接続されるのは望ましくありません。この問題を解決するには、optionsオブジェクトの作成をEffect内に移動し、EffectがroomId文字列のみに依存するようにします:
依存関係リストを編集してoptions依存関係を削除することから始めなかったことに注意してください。それは間違いです。代わりに、依存関係が不要になるように周囲のコードを変更しました。依存関係リストは、Effectのコードで使用されるすべてのリアクティブな値のリストとして考えてください。何をそのリストに載せるかは意図的に選択するものではありません。リストはあなたのコードを記述します。依存関係リストを変更するには、コードを変更します。
カスタムフックでロジックを再利用する
ReactにはuseState、useContext、useEffectなどの組み込みフックがあります。時には、データの取得、ユーザーがオンラインかどうかの追跡、チャットルームへの接続など、より特定の目的のためのフックがあればと思うこともあるでしょう。そのためには、アプリケーションのニーズに合わせて独自のフックを作成できます。
この例では、カスタムフックusePointerPositionがカーソル位置を追跡し、カスタムフックuseDelayedValueは渡された値から一定のミリ秒数だけ「遅れた」値を返します。サンドボックスのプレビューエリア上でカーソルを動かすと、カーソルに追従する動く点の軌跡が見えます:
カスタムフックを作成し、それらを組み合わせ、データをやり取りし、コンポーネント間で再利用できます。アプリが成長するにつれ、すでに作成したカスタムフックを再利用できるようになるため、手動で書くEffectは少なくなります。Reactコミュニティによってメンテナンスされている優れたカスタムフックも数多くあります。
