バージョン16.8においてHooksがリリースされました。Hooksによって、クラスでコンポーネントを記述する代わりに関数型のコンポーネント中にStateや他のReactの機能を使用できるようになります。VideoとReactによるビデオチャットアプリケーションを構築します。必要なものこのビデオチャットアプリケーションの構築には、以下のものが必要になります。(Twilioアカウントはこちらから無料でサインアップ)上記がひととおり揃ったら、開発環境の準備をしていきます。appを使用して、すぐに開発をはじめることができます。新規のディレクトリーにこのスターターアプリケーションの「twilio」ブランチをダウンロードまたはクローンして、依存関係をインストールしてください。install.env.exampleファイルを.envという名前でコピーします。.envアプリケーションを実行して、すべて問題なく動作しているか確認してください。dev下図のようなページがブラウザーにロードされることを確認してください。TwilioコンソールからアカウントSIDをコピーして、.envファイルのTWILIO_ACCOUNT_SIDに入力してください。APIキーおよびシークレットも必要になります。Videoツール内で作成することができます。キーペアを作成し、SIDおよびシークレットを.envファイルのTWILIO_API_KEYおよびTWILIO_API_SECRETにそれぞれ追加してください。スタイルを追加する本稿ではCSSについて取り上げるわけではありませんが、若干のCSSを追加して見栄えを少し良くしておくことをオススメします。src/App.cssファイルの内容をこちらのURLのCSSをコピーしたものに置き換えます。これで開発の準備が整いました。コンポーネントの計画を練るヘッダーおよびフッター、そしてVideoChatコンポーネントをレイアウトするAppコンポーネントから始めます。ViddeoChatコンポーネント内ではユーザーが名前と参加したいルームを入力できるLobbyコンポーネントを表示します。ユーザーがこれらを入力し終えたら、このLobbyコンポーネントからルームへの接続とビデオチャットの参加者を表示するRoomコンポーネントへと置き換えます。最後に、ルーム内の各参加者用のメディアの表示を処理するParticipantコンポーネントをレンダリングします。コンポートネントの構築Appコンポーネントsrc/App.jsファイルを開くと、初期状態のサンプルアプリケーションのコードが多くあるのでこれをまず削除します。Appコンポーネントはクラスベースのコンポーネントです。冒頭で今回のアプリケーション全体を関数型で構築すると説明したように変更することをオススメします。インポートのセクションから、Componentおよびlogo.svgのインポートを取り除いてください。続いてAppクラス全体を、今回のアプリケーションの骨組みをレンダリングする関数に置き換えます。ファイル全体は以下のようになります。App;VideoChatコンポーネントこのコンポーネントはユーザーがユーザー名およびルーム名を入力したかどうかに応じてロビーまたはルームを表示します。src/VideoChat.jsという名前で新しいコンポーネントファイルを作成し、下記のボイラープレートコードをペーストするところから始めてください。VideoChat;VideoChatコンポーネントはチャットに関連するデータを扱うための最上位階層のコンポーネントになります。ここではチャットに参加するユーザーのユーザー名、接続先のルームのルーム名、そしてサーバーから取得したアクセストークンを保存しておくことが必要になります。続くコンポーネントではこうしたデータを入力するためのフォームを組み立てていくことになります。Hookを使用してこのデータ保存を行います。useStateuseStateはStateの初期値を単一引数に取り、そして現在のState、およびこのStateを更新するための関数を含む配列を返す関数です。すると、stateおよびsetStateといったような2つの個別の変数に分けることができます。ここではsetStateを使用して、コンポーネント内のユーザー名、ルーム、およびトークンを捕捉します。ReactからuseStateをインポートして、ユーザー名、ルーム、そしてトークンをセットアップします。later};次に、ユーザーがusernameおよびroomNameを対応するinput要素に入力した際に、これを処理するための2つの関数が必要になります。Hookを使用してコンポーネントを最適化することができます。useCallbackこの関数型コンポーネントが呼び出されると、handle〜という関数が毎回再定義されます。これら関数はsetUsernameおよびsetRoomName関数に依存するためコンポーネントの一部である必要がありますが、その内容は毎回同じになります。useCallbackはメモ化対象の関数、および関数の依存関係を含む配列という2つの引数を取ります。関数の依存関係のいずれかが変更されると、メモ化された関数は期限切れと見なされ、関数は再定義されて再びメモ化されます。HookのsetState関数は、関数内の定数と考えることができます)。関数を書き換えるには、useCallbackをファイルの先頭部分のインポートに追加し、続いて対象の各関数をuseCallbackで囲みます。later};ユーザーがフォームを送信すると、ユーザー名とルーム名をサーバーに送信して、ルームに入室するためのアクセストークンと交換しましょう。これを行う関数もコンポーネント内に作成します。APIを使用してエンドポイントに対してJSONデータを送信し、レスポンスを受信、パースを行い、そしてsetToken関数を使用してStateにトークンを保存します。この関数もまた、useCallbackで囲むことになりますが、今回usernameとroomNameに依存しますので、これらをuseCallbackに対して依存関係として追加します。later};このコンポーネントの最後の関数として、ログアウト機能を追加しましょう。これでユーザーをルームから退出させてロビーに戻します。これを行うにはトークンをnullに設定します。ここでもこの関数をuseCallbackで囲みます。依存関係はありません。later};このコンポーネントの仕事の大半は配下のコンポーネントを取りまとめることです。そのためこれらコンポーネントを作成するまではレンダリング内容はごくわずかです。続いて、ユーザー名とルーム名の入力用フォームをレンダリングするLobbyコンポーネントを作成することにしましょう。Lobbyコンポーネントsrc/Lobby.jsという名前の新規ファイルを作成します。このコンポーネントでは親であるVideoChatコンポーネントに対して全イベントを渡すため、それ自体にデータを一切保存しておく必要はありません。コンポーネントのレンダリング時に、usernanmeおよびroomName、そしてこれらの変更とフォーム送信を処理する関数を渡します。これらのpropsを分割代入して、後々使いやすくなるようにしておくことができます。Lobbyコンポーネントの主な仕事は、下記のようにこれらのpropsを使用するフォームをレンダリングすることです。Lobby;videoChatコンポーネントを更新して、トークンを持っていない場合はLobbyをレンダリングしてください。トークンがある場合はusername、roomName、そしてtokenをレンダリングします。ここではファイルの冒頭でLobbyコンポーネントをインポートし、コンポーネント関数の下部にJSXをレンダリングすることが必要になります。render;};これをページ上に表示させるには、VideoChatコンポーネントをAppコンポーネントにインポートし、レンダリングを行うことが必要です。src/App.jsを再度開いて、下記の変更を行ってください。ことを確認し、ブラウザーでページを開くとフォームが確認できます。ユーザー名とルーム名を入力して送信を行うと、入力された各種の名前とサーバーから取得されたトークンの表示に変更されることとなります。Videoのチャットルームに参加できるようになります。SDKのインストールが必要になります。--savesrcディレクトリーにRoom.jsという名前の新規ファイルを作成します。下記のボイラープレートコードから始めていきます。Hookを使用します。roomName、token、そしてhandleLogoutを親コンポーネントからpropsとして取得します。Videoサービスへの接続です。接続が行われると、Stateとして保存すべきroomオブジェクトを取得します。roomには時間の経過に伴って変化する参加者のリストも含まれますので、これも保存しておく必要があります。それにはuseStateを使用します。初期値はルームに対してはnull、参加者に対しては空の配列を使用します。useState([]);});ルームに参加できるようになるにするため、このコンポーネントに何かレンダリングしてください。participants配列に対してmap関数を適用し、各参加者の識別子を表示し、加えてルーム内のローカル参加者の識別子も表示します。);});VideoChatコンポーネントを更新して、これまでのプレースホルダー情報の代わりにRoomコンポーネントをレンダリングしてください。render;};これをブラウザーで実行するとルーム名とログアウトボタンが表示されますが、まだルームに接続および参加をしていないため、参加者の識別子は表示されません。ルームへの参加に必要な情報をすべて持っているので、コンポーネントの初回レンダリング時に接続のアクションをトリガーさせます。ルームからの退出を行った方が良いです。これら2つの処理には副作用があります。Hookを使用します。引数に取り、これはコンポーネントがレンダリングされると実行されます。ここではコンポーネントのロード時にビデオサービスに接続を行うので、ルームへの参加者の入退室が行われる際にStateからの参加者の追加と削除をそれぞれ実行できる関数も必要になります。Room.jsファイル内のJSXの手前に、下記のコードを追加してHookの組み込みを始めてください。Videoサービスへの接続を行っています。Stateの設定、接続または切断してくる参加者に対するリスナーのセットアップ、そして参加者がいる場合はそれらをループで回して、先に記述したparticipantConnected関数を使用してparticipants配列Stateへの追加を行います。ここまでは順調ですが、コンポーネントが削除されてもルームには接続されたままです。そのためクリーンアップについても自前で行わなければなりません。useEffectに渡したコールバックの戻り値として関数を返すと、この関数はコンポーネントがアンマウントされるタイミングで実行されます。useEffectを使用するコンポーネントが再レンダリングされるとき、この関数もコールされ再実行前にEffectのクリーンアップが行われます。接続済みの場合にルームから切断する関数を返します。});ここでは先のuseStateから取得した、コールバック版のsetRoom関数を使用していることに注目してください。setRoomに対して関数を渡すと、この関数は直前の値を引数にコールされるので、ここではcurrentRoomという名前にしてください。関数から返される値は新たなStateとして設定されます。これでまだ終わりではありません。現状では再レンダリングのたびに毎回、コンポーネントは参加済みのルームから退出してから再接続します。これは理想的ではないので、クリーンナップを行い、Effectを再度実行すべきタイミングを指示してあげる必要があります。useCallbackで行なったように、依存関係のある変数の配列を渡してこれを行います。変数の値が変更されたらまずクリーンアップを行い、それからEffectを再実行します。変更がなかった場合はEffectの再実行は必要ありません。関数を見てみると、他のルームに、あるいは別のユーザーとして接続する場合には、roomNameまたはtokenの変更が見込まれることが分かります。これら変数をuseEffectに配列として渡すことにしましょう。token]);このEffectにおいては、コールバック関数を2つ定義していることに注目してください。これを先に行ったようにuseCallbackで囲む必要があるのではないかとお考えになるかもしれませんが、それは違います。これらはEffectの一部であるため、依存関係の更新時のみに実行されるためです。Hookの中でコールバック関数を使用することはできません。これらは必ず、コンポーネント内から直接、あるいはカスタムHookから使用されなければなりません。これでこのコンポーネントはほぼ出来上がりです。アプリケーションをリロードし、ユーザー名とルーム名を入力して、ここまで問題なく動作するか確認してください。ルームに入室すると識別子の表示が確認できます。ログアウトボタンをクリックするとロビーに戻ります。残る作業はビデオ通話の参加者のレンダリングと、そのビデオおよびオーディオのページへの追加です。ParticipantコンポーネントsrcディレクトリーにParticipant.jsという名前で新しいコンポーネントを作成します。ここでもこれまでと同様のボイラープレートコードからスタートします。このコンポーネントでは3つのHookを使用します。このうちuseStateとuseEffectについてはすでに見てきました。残る1つはuseRefです。propsのparticipantオブジェクトも渡し、useStateを用いて参加者のビデオおよびオーディオを状態を捕捉しています。要素に紐付けることが必要になります。直接アクセスを行うことはできません。そのため別の方法でHTML要素への参照を取得することが必要です。HookによってDOMへのアクセスを提供しています。Refsを使用するには、前もってこれを宣言し、JSX内で参照することが必要です。Hookを使用してrefsを作成します。});ここでは必要なJSXを返します。JSXの要素をrefに接続するには、ref属性を使用します。(これでテスト中にハウリングが発生して難聴に見舞われることもなくなります。Effectの使用がいくつか必要なため、このコンポーネントはまだあまり役に立ちません。Hookを3回使用します。その理由については、下記で説明していきます。Hookでは、Stateにビデオおよびオーディオトラックを設定し、トラックの追加または削除用にparticipantオブジェクトに対してリスナーをセットアップします。コンポーネントのアンマウント時にはこれらリスナーをクリーンアップおよび削除し、Stateを空にすることも必要になります。Hookでは、トラックがparticipantから追加または削除されたときに実行される2つの関数を追加します。これらの関数はいずれも、トラックがオーディオであるかビデオであるかを確認し、それから関連するState関数を使用して、これをStateから追加または削除します。come続いてparticipantオブジェクトを使用して、オーディオとビデオトラック用の初期値を設定します。先ほど記述した関数を使用してtrackSubscribedおよびtrackUnsubscribedイベントへのリスナーをセットアップ、そして返却される関数内でクリーンアップを行います。className="participant">Hookはparticipantオブジェクトにのみ依存しているため、これが変更されないかぎりクリーンアップは実行されない点に注目してください。Hookを使用してビデオおよびオーディオトラックをDOMに紐づけることも必要になります。ここではそのビデオバージョンだけお見せしますが、オーディオについても同様です。Videoをaudioに置き換えてください。HookはStateから最初のビデオトラックが存在すればこれを取得し、あらかじめrefによってキャプチャーされたDOMノードに紐付けます。VideoRef.currentを使用してref中の現在のDOMノードを参照することができます。ビデオトラックを紐づける場合、クリーンアップ時にこれを紐付け解除する関数を返却することも必要になります。className="participant">audioTracksに対しても同様にHookを用意すれば、RoomコンポーネントからParticipantコンポーネントに対してレンダリングを行う準備が整います。Participantコンポーネントをファイル冒頭でインポートし、識別子表示用の段落を実際のコンポーネントに置き換えます。);});ここでアプリケーションをリロードすれば、ルームに参加してご自身の映像を画面上で確認できます。別のブラウザーを開いて同じルームに参加すれば、もうひとつ同じ映像を確認できます。ログアウトボタンをクリックすればロビーに戻ることができます。Videoの構築には、対処すべきあらゆる副作用が存在するため、いくらかの追加作業が必要になります。要素へ接続するためのDOM操作まで、考慮すべきポイントが多々あります。本稿ではuseState、useCallback、useEffect、およびuseRefを使用してこれらの副作用を制御し、関数型のコンポーネントのみを使用してアプリケーションを構築する方法について見てきました。Hooks双方の理解の助けになれば幸いです。このアプリケーションの全ソースコードはGitHub上で公開されていますので、実際に触って理解を深めてみてください。