React HooksによるTwilioビデオチャットの構築方法
以前、本ブログでReactを使用したビデオチャット (英文記事) について取り上げましたが、その後 (Reactの) バージョン16.8においてHooksがリリースされました。Hooksによって、クラスでコンポーネントを記述する代わりに関数型のコンポーネント中にStateや他のReactの機能を使用できるようになります。
本稿では、useState
, useCallback
, useEffect
, useRef
Hooksによる関数型コンポーネントのみを使用して、Twilio VideoとReactによるビデオチャットアプリケーションを構築します。
必要なもの
このビデオチャットアプリケーションの構築には、以下のものが必要になります。
- Node.jsおよびnpmのインストール
- Twilioのアカウント (Twilioアカウントはこちらから無料でサインアップ)
上記がひととおり揃ったら、開発環境の準備をしていきます。
早速始めてみる
筆者の作成したReact and Express Starter appを使用して、すぐに開発をはじめることができます。 新規のディレクトリーにこのスターターアプリケーションの「twilio」ブランチをダウンロードまたはクローンして、依存関係をインストールしてください。
git clone -b twilio git@github.com:philnash/react-express-starter.git
twilio-video-react-hooks
cd twilio-video-react-hooks
npm install
.env.example
ファイルを.env
という名前でコピーします。
cp .env.example .env
アプリケーションを実行して、すべて問題なく動作しているか確認してください。
npm run dev
下図のようなページがブラウザーにロードされることを確認してください。

Twilioのクレデンシャルを準備する
Twilio Videoに接続するには、いくつかのクレデンシャル (認証情報) が必要になります。
TwilioコンソールからアカウントSIDをコピーして、.env
ファイルのTWILIO_ACCOUNT_SID
に入力してください。
また、APIキーおよびシークレットも必要になります。 これらは、コンソール内のProgrammable 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クラス全体を、今回のアプリケーションの骨組みをレンダリングする関数に置き換えます。
ファイル全体は以下のようになります。
import React from 'react';
import React from 'react';
import './App.css';
const App = () => {
return (
<div className="app">
<header>
<h1>Video Chat with Hooks</h1>
</header>
<main>
<p>VideoChat goes here.</p>
</main>
<footer>
<p>
Made with{' '}
<span role="img" aria-label="React">
⚛
</span>{' '}
by <a href="https://twitter.com/philnash">philnash</a>
</p>
</footer>
</div>
);
};
export default App;
VideoChatコンポーネント
このコンポーネントはユーザーがユーザー名およびルーム名を入力したかどうかに応じてロビーまたはルームを表示します。
src/VideoChat.js
という名前で新しいコンポーネントファイルを作成し、下記のボイラープレートコードをペーストするところから始めてください。
import React from 'react';
const VideoChat = () => {
return <div></div> // we'll build up our response later
};
export default VideoChat;
VideoChat
コンポーネントはチャットに関連するデータを扱うための最上位階層のコンポーネントになります。
ここではチャットに参加するユーザーのユーザー名、接続先のルームのルーム名、そしてサーバーから取得したアクセストークンを保存しておくことが必要になります。
続くコンポーネントではこうしたデータを入力するためのフォームを組み立てていくことになります。
React Hooksでは、useState Hookを使用してこのデータ保存を行います。
useState
useStateはState
の初期値を単一引数に取り、そして現在のState、およびこのState
を更新するための関数を含む配列を返す関数です。
この配列を分割代入 (Destructuring) すると、state
およびsetState
といったような2つの個別の変数に分けることができます。
ここではsetState
を使用して、コンポーネント内のユーザー名、ルーム、およびトークンを捕捉します。
まずはReactからuseState
をインポートして、ユーザー名、ルーム、そしてトークンをセットアップします。
import React, { useState } from 'react';
const VideoChat = () => {
const [username, setUsername] = useState('');
const [roomName, setRoomName] = useState('');
const [token, setToken] = useState(null);
return <div></div> // we'll build up our response later
};
次に、ユーザーがusername
およびroomName
を対応するinput要素に入力した際に、これを処理するための2つの関数が必要になります。
import React, { useState } from 'react';
const VideoChat = () => {
const [username, setUsername] = useState('');
const [roomName, setRoomName] = useState('');
const [token, setToken] = useState(null);
const handleUsernameChange = event => {
setUsername(event.target.value);
};
const handleRoomNameChange = event => {
setRoomName(event.target.value);
};
return <div></div> // we'll build up our response later
};
これはこれで動作しますが、useCallbackという別のReact Hookを使用してコンポーネントを最適化することができます。
useCallback
この関数型コンポーネントが呼び出されると、handle〜という関数が毎回再定義されます。
これら関数はsetUsername
およびsetRoomName
関数に依存するためコンポーネントの一部である必要がありますが、その内容は毎回同じになります。
useCallback
は、関数をメモ化しておくことを可能にするReact Hookです。
useCallback
はメモ化対象の関数、および関数の依存関係を含む配列という2つの引数を取ります。
関数の依存関係のいずれかが変更されると、メモ化された関数は期限切れと見なされ、関数は再定義されて再びメモ化されます。
ここでは2つの関数にはいずれも依存関係がないため、空の配列を渡すだけで問題ありません (useState HookのsetState関数は、関数内の定数と考えることができます) 。
関数を書き換えるには、useCallback
をファイルの先頭部分のインポートに追加し、続いて対象の各関数をuseCallback
で囲みます。
import React, { useState, useCallback } from 'react';
const VideoChat = () => {
const [username, setUsername] = useState('');
const [roomName, setRoomName] = useState('');
const [token, setToken] = useState(null);
const handleUsernameChange = useCallback(event => {
setUsername(event.target.value);
}, []);
const handleRoomNameChange = useCallback(event => {
setRoomName(event.target.value);
}, []);
return <div></div> // we'll build up our response later
};
ユーザーがフォームを送信すると、ユーザー名とルーム名をサーバーに送信して、ルームに入室するためのアクセストークンと交換しましょう。これを行う関数もコンポーネント内に作成します。
ここではfetch APIを使用してエンドポイントに対してJSONデータを送信し、レスポンスを受信、パースを行い、そしてsetToken
関数を使用してState
にトークンを保存します。 この関数もまた、useCallback
で囲むことになりますが、今回username
とroomName
に依存しますので、これらをuseCallback
に対して依存関係として追加します。
const handleRoomNameChange = useCallback(event => {
setRoomName(event.target.value);
}, []);
const handleSubmit = useCallback(async event => {
event.preventDefault();
const data = await fetch('/video/token', {
method: 'POST',
body: JSON.stringify({
identity: username,
room: roomName
}),
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json());
setToken(data.token);
}, [username, roomName]);
return <div></div>// we'll build up our response later
};
このコンポーネントの最後の関数として、ログアウト機能を追加しましょう。
これでユーザーをルームから退出させてロビーに戻します。
これを行うにはトークンをnull
に設定します。
ここでもこの関数をuseCallback
で囲みます。依存関係はありません。
const handleLogout = useCallback(event => {
setToken(null);
}, []);
return <div></div> // we'll build up our response later
};
このコンポーネントの仕事の大半は配下のコンポーネントを取りまとめることです。
そのためこれらコンポーネントを作成するまではレンダリング内容はごくわずかです。
続いて、ユーザー名とルーム名の入力用フォームをレンダリングするLobby
コンポーネントを作成することにしましょう。
Lobbyコンポーネント
src/Lobby.js
という名前の新規ファイルを作成します。
このコンポーネントでは親であるVideoChat
コンポーネントに対して全イベントを渡すため、それ自体にデータを一切保存しておく必要はありません。
コンポーネントのレンダリング時に、usernanme
およびroomName
、そしてこれらの変更とフォーム送信を処理する関数を渡します。
これらのprops
を分割代入して、後々使いやすくなるようにしておくことができます。
Lobby
コンポーネントの主な仕事は、下記のようにこれらのprops
を使用するフォームをレンダリングすることです。
import React from 'react';
const Lobby = ({
username,
handleUsernameChange,
roomName,
handleRoomNameChange,
handleSubmit
}) => {
return (
<form onSubmit={handleSubmit}>
<h2>Enter a room</h2>
<div>
<label htmlFor="name">Name:</label>
<input
type="text"
id="field"
value={username}
onChange={handleUsernameChange}
required
/>
</div>
<div>
<label htmlFor="room">Room name:</label>
<input
type="text"
id="room"
value={roomName}
onChange={handleRoomNameChange}
required
/>
</div>
<button type="submit">Submit</button>
</form>
);
};
export default Lobby;
videoChat
コンポーネントを更新して、トークンを持っていない場合はLobbyをレンダリングしてください。
トークンがある場合はusername
、roomName
、そしてtoken
をレンダリングします。
ここではファイルの冒頭でLobby
コンポーネントをインポートし、コンポーネント関数の下部にJSXをレンダリングすることが必要になります。
import React, { useState, useCallback } from 'react';
import Lobby from './Lobby';
const VideoChat = () => {
// ...
const handleLogout = useCallback(event => {
setToken(null);
}, []);
let render;
if (token) {
render = (
<div>
<p>Username: {username}</p>
<p>Room name: {roomName}</p>
<p>Token: {token}</p>
</div>
);
} else {
render = (
<Lobby
username={username}
roomName={roomName}
handleUsernameChange={handleUsernameChange}
handleRoomNameChange={handleRoomNameChange}
handleSubmit={handleSubmit}
/>
);
}
return render;
};
これをページ上に表示させるには、VideoChatコンポーネントをAppコンポーネントにインポートし、レンダリングを行うことが必要です。
src/App.js
を再度開いて、下記の変更を行ってください。
import React from 'react';
import './App.css';
import VideoChat from './VideoChat';
const App = () => {
return (
<div className="app">
<header>
<h1>Video Chat with Hooks</h1>
</header>
<main>
<VideoChat />
</main>
<footer>
<p>
Made with{' '}
<span role="img" aria-label="React">
⚛️
</span>{' '}
by <a href="https://twitter.com/philnash">philnash</a>
</p>
</footer>
</div>
);
};
export default App;
アプリケーションがまだ実行中である (またはnpm run devコマンドで再起動している) ことを確認し、ブラウザーでページを開くとフォームが確認できます。
ユーザー名とルーム名を入力して送信を行うと、入力された各種の名前とサーバーから取得されたトークンの表示に変更されることとなります。

Roomコンポーネント
アプリケーションにユーザー名とルーム名を追加したので、これらを使用してTwilio Videoのチャットルームに参加できるようになります。
Twilio Videoサービスと連携するには、下記コマンドを使用したJavaScript SDKのインストールが必要になります。
npm install twilio-video --save
src
ディレクトリーにRoom.js
という名前の新規ファイルを作成します。
下記のボイラープレートコードから始めていきます。
このコンポーネントではTwilio Video SDKとuseState
およびuseEffect
Hookを使用します。
またroomName
、token
、そしてhandleLogout
を親コンポーネントからprops
として取得します。
import React, { useState, useEffect } from 'react';
import Video from 'twilio-video';
const Room = ({ roomName, token, handleLogout }) => {
});
export default Room;
コンポーネントがまず行うことは、token
とroomName
を使用したTwilio Videoサービスへの接続です。
接続が行われると、State
として保存すべきroom
オブジェクトを取得します。
room
には時間の経過に伴って変化する参加者のリストも含まれますので、これも保存しておく必要があります。
それにはuseState
を使用します。 初期値はルームに対してはnull
、参加者に対しては空の配列を使用します。
const Room = ({ roomName, token, handleLogout }) => {
const [room, setRoom] = useState(null);
const [participants, setParticipants] = useState([]);
});
ルームに参加できるようになるにするため、このコンポーネントに何かレンダリングしてください。
participants配列に対してmap関数を適用し、各参加者の識別子を表示し、加えてルーム内のローカル参加者の識別子も表示します。
const Room = ({ roomName, token, handleLogout }) => {
const [room, setRoom] = useState(null);
const [participants, setParticipants] = useState([]);
const remoteParticipants = participants.map(participant => (
<p key={participant.sid}>participant.identity</p>
));
return (
<div className="room">
<h2>Room: {roomName}</h2>
<button onClick={handleLogout}>Log out</button>
<div className="local-participant">
{room ? (
<p key={room.localParticipant.sid}>{room.localParticipant.identity}</p>
) : (
''
)}
</div>
<h3>Remote Participants</h3>
<div className="remote-participants">{remoteParticipants}</div>
</div>
);
});
VideoChatコンポーネントを更新して、これまでのプレースホルダー情報の代わりにRoomコンポーネントをレンダリングしてください。
import React, { useState, useCallback } from 'react';
import Lobby from './Lobby';
import Room from './Room';
const VideoChat = () => {
// ...
const handleLogout = useCallback(event => {
setToken(null);
}, []);
let render;
if (token) {
render = (
<Room roomName={roomName} token={token} handleLogout={handleLogout} />
);
} else {
render = (
<Lobby
username={username}
roomName={roomName}
handleUsernameChange={handleUsernameChange}
handleRoomNameChange={handleRoomNameChange}
handleSubmit={handleSubmit}
/>
);
}
return render;
};
これをブラウザーで実行するとルーム名とログアウトボタンが表示されますが、まだルームに接続および参加をしていないため、参加者の識別子は表示されません。

すでにルームへの参加に必要な情報をすべて持っているので、コンポーネントの初回レンダリング時に接続のアクションをトリガーさせます。
またコンポーネントの破棄時には (これ以上WebRTC接続をバックグラウンドで維持しておく意味がないため) 、ルームからの退出を行った方が良いです。
これら2つの処理には副作用があります。
クラスベースのコンポーネントでは、このような状況ではcomponentDidMount
およびcomponentWillUnmount
ライフサイクルメソッドが使用されますが、React HooksではuseEffect
Hookを使用します。
useEffect
useEffectは関数 (メソッド) を引数に取り、これはコンポーネントがレンダリングされると実行されます。
ここではコンポーネントのロード時にビデオサービスに接続を行うので、ルームへの参加者の入退室が行われる際にState
からの参加者の追加と削除をそれぞれ実行できる関数も必要になります。
Room.js
ファイル内のJSXの手前に、下記のコードを追加してHookの組み込みを始めてください。
useEffect(() => {
const participantConnected = participant => {
setParticipants(prevParticipants => [...prevParticipants, participant]);
};
const participantDisconnected = participant => {
setParticipants(prevParticipants =>
prevParticipants.filter(p => p !== participant)
);
};
Video.connect(token, {
name: roomName
}).then(room => {
setRoom(room);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.participants.forEach(participantConnected);
});
});
ここではtoken
とroomName
を使用してTwilio Videoサービスへの接続を行っています。
接続が完了すると、room State
の設定、接続または切断してくる参加者に対するリスナーのセットアップ、そして参加者がいる場合はそれらをループで回して、先に記述したparticipantConnected関数を使用してparticipants配列Stateへの追加を行います。
ここまでは順調ですが、コンポーネントが削除されてもルームには接続されたままです。 そのためクリーンアップについても自前で行わなければなりません。
useEffect
に渡したコールバックの戻り値として関数を返すと、この関数はコンポーネントがアンマウントされるタイミングで実行されます。 useEffect
を使用するコンポーネントが再レンダリングされるとき、この関数もコールされ再実行前にEffectのクリーンアップが行われます。
接続済みの場合にルームから切断する関数を返します。
Video.connect(token, {
name: roomName
}).then(room => {
setRoom(room);
room.on('participantConnected', participantConnected);
room.participants.forEach(participantConnected);
});
return () => {
setRoom(currentRoom => {
if (currentRoom && currentRoom.localParticipant.state === 'connected') {
currentRoom.disconnect();
return null;
} else {
return currentRoom;
}
});
};
});
ここでは先のuseState
から取得した、コールバック版のsetRoom関数を使用していることに注目してください。
setRoom
に対して関数を渡すと、この関数は直前の値を引数にコールされるので、ここではcurrentRoom
という名前にしてください。
一方、関数から返される値は新たなStateとして設定されます。
しかし、これでまだ終わりではありません。
現状では再レンダリングのたびに毎回、コンポーネントは参加済みのルームから退出してから再接続します。
これは理想的ではないので、クリーンナップを行い、Effect
を再度実行すべきタイミングを指示してあげる必要があります。
ちょうどuseCallback
で行なったように、依存関係のある変数の配列を渡してこれを行います。
変数の値が変更されたらまずクリーンアップを行い、それからEffectを再実行します。
変更がなかった場合はEffect
の再実行は必要ありません。
関数を見てみると、他のルームに、あるいは別のユーザーとして接続する場合には、roomName
またはtoken
の変更が見込まれることが分かります。 これら変数をuseEffect
に配列として渡すことにしましょう。
return () => {
setRoom(currentRoom => {
if (currentRoom && currentRoom.localParticipant.state === 'connected') {
currentRoom.disconnect();
return null;
} else {
return currentRoom;
}
});
};
}, [roomName, token]);
このEffect
においては、コールバック関数を2つ定義していることに注目してください。
これを先に行ったようにuseCallback
で囲む必要があるのではないかとお考えになるかもしれませんが、それは違います。
これらはEffect
の一部であるため、依存関係の更新時のみに実行されるためです。
また、Hookの中でコールバック関数を使用することはできません。
これらは必ず、コンポーネント内から直接、あるいはカスタムHookから使用されなければなりません。
これでこのコンポーネントはほぼ出来上がりです。 アプリケーションをリロードし、ユーザー名とルーム名を入力して、ここまで問題なく動作するか確認してください。
ルームに入室すると識別子の表示が確認できます。 ログアウトボタンをクリックするとロビーに戻ります。

残る作業はビデオ通話の参加者のレンダリングと、そのビデオおよびオーディオのページへの追加です。
Participantコンポーネント
src
ディレクトリーにParticipant.js
という名前で新しいコンポーネントを作成します。
ここでもこれまでと同様のボイラープレートコードからスタートします。
このコンポーネントでは3つのHook
を使用します。 このうちuseState
とuseEffect
についてはすでに見てきました。 残る1つはuseRef
です。
またpropsのparticipantオブジェクトも渡し、useState
を用いて参加者のビデオおよびオーディオを状態を捕捉しています。
import React, { useState, useEffect, useRef } from 'react';
const Participant = ({ participant }) => {
const [videoTracks, setVideoTracks] = useState([]);
const [audioTracks, setAudioTracks] = useState([]);
};
export default Participant;
参加者からビデオまたはオーディオストリームを取得した際には、これを <video>
および <audio>
要素に紐付けることが必要になります。
JSXは宣言的 (Declarative) であるため、DOM (Document Object Model) への直接アクセスを行うことはできません。
そのため別の方法でHTML要素への参照を取得することが必要です。
ReactではrefsおよびuseRef HookによってDOMへのアクセスを提供しています。
Refsを使用するには、前もってこれを宣言し、JSX内で参照することが必要です。
ここでは、レンダリング前にuseRef Hookを使用してrefsを作成します。
const Participant = ({ participant }) => {
const [videoTracks, setVideoTracks] = useState([]);
const [audioTracks, setAudioTracks] = useState([]);
const videoRef = useRef();
const audioRef = useRef();
});
ここでは必要なJSXを返します。
JSXの要素をrefに接続するには、ref属性を使用します。
const Participant = ({ participant }) => {
const [videoTracks, setVideoTracks] = useState([]);
const [audioTracks, setAudioTracks] = useState([]);
const videoRef = useRef();
const audioRef = useRef();
return (
<div className="participant">
<h3>{participant.identity}</h3>
<video ref={videoRef} autoPlay={true} />
<audio ref={audioRef} autoPlay={true} muted={true} />
</div>
);
});
また <video>
および <audio>
タグの属性をautoplay (これにより、メディアストリームを取得するとすぐに再生が開始されます) およびmuted (これでテスト中にハウリングが発生して難聴に見舞われることもなくなります。 この過ちを犯したとき、きっと筆者に感謝すること請け合いです) に設定します。
Effect
の使用がいくつか必要なため、このコンポーネントはまだあまり役に立ちません。
実際にはこのコンポーネントではuseEffect
Hookを3回使用します。
その理由については、下記で説明していきます。
最初のuseEffect
Hookでは、State
にビデオおよびオーディオトラックを設定し、トラックの追加または削除用にparticipant
オブジェクトに対してリスナーをセットアップします。
またコンポーネントのアンマウント時にはこれらリスナーをクリーンアップおよび削除し、State
を空にすることも必要になります。
最初のuseEffect
Hookでは、トラックがparticipant
から追加または削除されたときに実行される2つの関数を追加します。 これらの関数はいずれも、トラックがオーディオであるかビデオであるかを確認し、それから関連するState関数を使用して、これをStateから追加または削除します。
const videoRef = useRef();
const audioRef = useRef();
useEffect(() => {
const trackSubscribed = track => {
if (track.kind === 'video') {
setVideoTracks(videoTracks => [...videoTracks, track]);
} else {
setAudioTracks(audioTracks => [...audioTracks, track]);
}
};
const trackUnsubscribed = track => {
if (track.kind === 'video') {
setVideoTracks(videoTracks => videoTracks.filter(v => v !== track));
} else {
setAudioTracks(audioTracks => audioTracks.filter(a => a !== track));
}
};
// more to come
続いてparticipant
オブジェクトを使用して、オーディオとビデオトラック用の初期値を設定します。
先ほど記述した関数を使用してtrackSubscribed
およびtrackUnsubscribed
イベントへのリスナーをセットアップ、そして返却される関数内でクリーンアップを行います。
useEffect(() => {
const trackSubscribed = track => {
// implementation
};
const trackUnsubscribed = track => {
// implementation
};
setVideoTracks(Array.from(participant.videoTracks.values()));
setAudioTracks(Array.from(participant.audioTracks.values()));
participant.on('trackSubscribed', trackSubscribed);
participant.on('trackUnsubscribed', trackUnsubscribed);
return () => {
setVideoTracks([]);
setAudioTracks([]);
participant.removeAllListeners();
};
}, [participant]);
return (
<div className="participant">
Hookはparticipant
オブジェクトにのみ依存しているため、これが変更されないかぎりクリーンアップは実行されない点に注目してください。
また、useEffect
Hookを使用してビデオおよびオーディオトラックをDOMに紐づけることも必要になります。
ここではそのビデオバージョンだけお見せしますが、オーディオについても同様です。
Video
をaudio
に置き換えてください。
HookはState
から最初のビデオトラックが存在すればこれを取得し、あらかじめrefによってキャプチャーされたDOMノードに紐付けます。
VideoRef.currentを使用してref中の現在のDOMノードを参照することができます。
ビデオトラックを紐づける場合、クリーンアップ時にこれを紐付け解除する関数を返却することも必要になります。
}, [participant]);
useEffect(() => {
const videoTrack = videoTracks[0];
if (videoTrack) {
videoTrack.attach(videoRef.current);
return () => {
videoTrack.detach();
};
}
}, [videoTracks]);
return (
<div className="participant">
audioTracks
に対しても同様にHookを用意すれば、Room
コンポーネントからParticipant
コンポーネントに対してレンダリングを行う準備が整います。
Participant
コンポーネントをファイル冒頭でインポートし、識別子表示用の段落を実際のコンポーネントに置き換えます。
import React, { useState, useEffect } from 'react';
import Video from 'twilio-video';
import Participant from './Participant';
// hooks here
const remoteParticipants = participants.map(participant => (
<Participant key={participant.sid} participant={participant} />
));
return (
<div className="room">
<h2>Room: {roomName}</h2>
<button onClick={handleLogout}>Log out</button>
<div className="local-participant">
{room ? (
<Participant
key={room.localParticipant.sid}
participant={room.localParticipant}
/>
) : (
''
)}
</div>
<h3>Remote Participants</h3>
<div className="remote-participants">{remoteParticipants}</div>
</div>
);
});
ここでアプリケーションをリロードすれば、ルームに参加してご自身の映像を画面上で確認できます。
別のブラウザーを開いて同じルームに参加すれば、もうひとつ同じ映像を確認できます。
ログアウトボタンをクリックすればロビーに戻ることができます。

まとめ
Reactを使用したTwilio Videoの構築には、対処すべきあらゆる副作用が存在するため、いくらかの追加作業が必要になります。
トークン取得のためのリクエストの発行からVideoサービスへの接続、<video>
および <audio>
要素へ接続するためのDOM操作まで、考慮すべきポイントが多々あります。
本稿ではuseState
、useCallback
、useEffect
、およびuseRef
を使用してこれらの副作用を制御し、関数型のコンポーネントのみを使用してアプリケーションを構築する方法について見てきました。
本稿がTwilio Video、そしてReact Hooks双方の理解の助けになれば幸いです。
このアプリケーションの全ソースコードはGitHub上で公開されていますので、実際に触って理解を深めてみてください。
React Hooksについてのさらに詳しい読み物としては、非常に詳細な公式ドキュメント、Hooksの仕組みが視覚的に解説されているthinking in hooks (英文記事)、そしてDan Abramov氏のDeep dive into useEffect (長い英文記事ですが相応の価値のあるものです) を参照してください。
Twilio Videoを使用した開発について学習したい場合は、Switching cameras during a Video chat または、Add screen sharing to your Twilio Video application (いずれも英文記事) をご覧ください。
Reactを使用してこれらのアプリケーション、あるいは他のクールなビデオチャット機能を構築したなら、ぜひ筆者宛にTwitter上のコメントやphilnash@twilio.comのメールでお知らせください。