Erstellen eines Videochats mit React-Hooks
Lesezeit: 11 Minuten
Wir haben im Rahmen dieses Blogs bereits einen Videochat in React erstellt, aber mittlerweile wurden mit der Version 16.8 von React Hooks eingeführt. Mit Hooks können wir Zustands- oder andere React-Funktionen innerhalb von Funktionskomponenten nutzen, ohne dass wir eine Klasse schreiben müssen.
In diesem Post werden wir eine Videochat-Anwendung mit Twilio Video und React erstellen, die nur Funktionskomponenten enthält. Dazu nutzen wir die Hooks useState
, useCallback
, useEffect
und useRef
.
Das brauchen wir
Um diese Videochat-Anwendung zu erstellen, brauchen wir Folgendes:
- Node.js und npm
- Ein Twilio-Konto (Melde dich hier für ein kostenloses Twilio-Konto an.)
Sobald wir das alles haben, können wir unsere Entwicklungsumgebung vorbereiten.
Los geht's
Damit wir direkt die React-Anwendung aufrufen können, beginnen wir mit der React- und Express-Starter-App, die ich erstellt habe. Wir laden den „twilio“-Zweig aus der Starter-App herunter oder klonen ihn, rufen das neue Verzeichnis auf und installieren die Abhängigkeiten:
Wir kopieren die Datei .env.example
in .env
.
Wir führen die Anwendung aus, um sicherzustellen, dass alles erwartungsgemäß funktioniert:
Wir sollten die folgende Seite in unserem Browser sehen:
Vorbereiten der Twilio-Anmeldedaten
Damit wir eine Verbindung zu Twilio Video herstellen können, benötigen wir Anmeldedaten. Wir kopieren aus der Twilio console unsere Konto-SID und geben diese in die Datei .env
als TWILIO_ACCOUNT_SID
ein.
Außerdem brauchen wir einen API-Schlüssel und ein API-Geheimnis. Diese können wir mit den Programmable Video-Tools in der Konsole erstellen. Wir erstellen ein Schlüsselpaar und fügen die SID und das Geheimnis als TWILIO_API_KEY
bzw. TWILIO_API_SECRET
in die Datei .env
ein.
Hinzufügen eines Stylesheets
In diesem Beitrag gehen wir zwar nicht näher auf CSS ein, aber damit das Ergebnis nicht ganz so schlimm aussieht, fügen wir ein Stylesheet hinzu. Wir holen uns das CSS von dieser URL und ersetzen den Inhalt unter src/App.css
durch dieses neue Stylesheet.
Jetzt haben wir alles, um mit dem Erstellen zu beginnen.
Planen von Komponenten
Am Anfang steht die App
-Komponente, in der wir einen Header und Footer für die App sowie eine VideoChat
-Komponente festlegen können. Innerhalb der VideoChat
-Komponente möchten wir eine Lobby
-Komponente anzeigen, in die der Benutzer seinen Namen und den Raum eingeben kann, dem er beitreten möchte. Nachdem der Benutzer diese Informationen eingegeben hat, ersetzen wir die Lobby
-Komponente durch eine Room
-Komponente. Diese stellt dann eine Verbindung zum Raum her und zeigt die Teilnehmer des Videochats an. Schließlich rendern wir für jeden Teilnehmer im Raum eine Participant
-Komponente, die dafür verantwortlich ist, die Medien der Teilnehmer anzuzeigen.
Erstellen von Komponenten
Die App-Komponente
Wir öffnen jetzt src/App.js
. Hier steht noch viel Code von der ursprünglichen Beispiel-App, den wir entfernen können. Außerdem müssen wir beachten, dass es sich bei der App
-Komponente um eine klassenbasierte Komponente handelt. Wir haben zu Beginn erwähnt, dass wir die gesamte App mit Funktionskomponenten erstellen, deshalb müssen wir das ändern.
Wir entfernen aus den Importen Component
sowie die importierte Datei „logo.svg“. Wir ersetzen die gesamte App-Klasse durch eine Funktion, die das Gerüst unserer Anwendung rendert. Die ganze Datei sollte in etwa so aussehen:
Die VideoChat-Komponente
Diese Komponente zeigt, je nachdem, ob der Benutzer einen Benutzernamen und Raumnamen eingegeben hat, entweder einen Wartebereich oder einen Raum an. Wir erstellen eine neue Komponentendatei src/VideoChat.js
und fügen an ihren Anfang den folgenden Textbaustein ein:
Die VideoChat
-Komponente ist die Komponente auf oberster Ebene, die die Daten zum Chat verarbeitet. Wir müssen außerdem einen Benutzernamen für den Benutzer, der dem Chat beitritt, einen Raumnamen für den Raum, zu dem er eine Verbindung herstellt, und ein Zugriffstoken speichern, sobald dieses vom Server abgerufen wurde. In der nächsten Komponente erstellen wir ein Formular, in das einige dieser Daten eingegeben werden.
Zum Speichern dieser Daten verwenden wir von den React-Hooks den useState
Hook.
useState
useState
ist eine Funktion, die ein Argument (Anfangszustand) benötigt und ein Array zurückgibt, das den aktuellen Zustand und eine Funktion zum Aktualisieren dieses Zustands enthält. Wir brechen die Struktur dieses Arrays auf. Dadurch erhalten wir zwei verschiedene Variablen, z. B. state
und setState
. Mit setState
verfolgen wir den Benutzernamen, den Raumnamen und das Token in unserer Komponente.
Wir importieren zuerst useState
aus React und richten dann die Zustände für den Benutzernamen, den Raumnamen und das Token ein:
Als Nächstes benötigen wir zwei Funktionen, die den username
und roomName
aktualisieren, wenn der Benutzer diese Informationen in die entsprechenden Eingabeelemente eingibt.
Das funktioniert zwar, aber wir können unsere Komponente optimieren, wenn wir hier einen weiteren React-Hook verwenden: useCallback
useCallback
Bei jedem Aufrufen dieser Funktionskomponente werden die handleXXX
-Funktionen neu definiert. Diese müssen Teil der Komponente sein, da sie von den Funktionen setUsername
und setRoomName
abhängen. Diese bleiben aber unverändert. useCallback
ist ein React-Hook, mit dem wir die Funktionen memoisieren können. Das heißt, wenn sie sich zwischen Funktionsaufrufen nicht geändert haben, werden sie nicht neu definiert.
useCallback
benötigt zwei Argumente: die Funktion, die memoisiert werden soll, und ein Array der Abhängigkeiten der Funktion. Wenn sich eine der Abhängigkeiten der Funktion ändert, deutet das darauf hin, dass die memoisierte Funktion nicht mehr aktuell ist. Die Funktion wird dann neu definiert und erneut memoisiert.
In diesem Fall gibt es für diese zwei Funktionen keine Abhängigkeiten, deshalb genügt ein leeres Array (die setState
-Funktionen des useState
-Hooks gelten als konstant innerhalb der Funktion). Um diese Funktion neu zu schreiben, müssen wir useCallback
dem Import am Anfang der Datei hinzufügen und dann jede einzelne dieser Funktionen umschließen.
Wenn der Benutzer das Formular übermittelt, sollen der Benutzername und Raumname an den Server gesendet und gegen ein Zugriffstoken ausgetauscht werden. Mit diesem Zugriffstoken können wir dem Raum beitreten. Diese Funktion werden wir ebenfalls in dieser Komponente erstellen.
Mithilfe der Fetch-API senden wir die Daten im JSON-Format an den Endpunkt, erhalten eine Antwort, die analysiert wird, und speichern dann mithilfe von setToken
das Token in unserem Zustand. Diese Funktion umschließen wir ebenfalls mit useCallback
, aber in diesem Fall ist die Funktion abhängig von username
und roomName
. Deshalb fügen wir diese beiden Elemente useCallback
als Abhängigkeiten hinzu.
Als letzte Funktion fügen wir dieser Komponente eine Abmeldefunktion hinzu. Dadurch wird der Benutzer von einem Raum abgemeldet, und er kehrt in den Wartebereich zurück. Hierzu setzen wir das Token auf den Wert null
. Das Ganze wird wiederum mit useCallback
ohne Abhängigkeiten umschlossen.
Diese Komponente orchestriert vorwiegend die Komponenten, die sich unterhalb befinden, deshalb gibt es erst etwas zu rendern, wenn wir diese Komponenten erstellt haben. Wir erstellen als Nächstes die Lobby-Komponente, mit der das Formular für die Eingabe von Benutzernamen und Raumnamen gerendert wird.
Die Lobby-Komponente
Wir erstellen unter src/Lobby.js
eine neue Datei. In dieser Komponente werden keine Daten gespeichert, da sie alle Ereignisse an die übergeordnete VideoChat-Komponente übergibt. Wenn die Komponente gerendert wird, werden an sie username
und roomName
sowie die Funktionen zur Verarbeitung von Änderungen übergeben. Die Komponente ist außerdem für das Senden des Formulars verantwortlich. Wir können die Struktur dieser Eigenschaften aufbrechen, um die spätere Verwendung zu vereinfachen.
Die wichtigste Aufgabe der Lobby
-Komponente ist das Rendern des Formulars anhand dieser Eigenschaften. Das sieht in etwa so aus:
Wir aktualisieren die VideoChat
-Komponente, um die Lobby
zu rendern, wenn wir kein token
haben. Ansonsten rendern wir username
, roomName
und token
. Wir müssen die Lobby
-Komponente am Anfang der Datei importieren und am Ende der Komponentenfunktion JSX rendern:
Damit das auch auf der Seite angezeigt wird, müssen wir außerdem die VideoChat
-Komponente in die App
-Komponente importieren und rendern. Wir öffnen erneut die Datei src/App.js
und nehmen darin folgende Änderungen vor:
Wir müssen sicherstellen, dass die App immer noch ausgeführt wird (oder wir starten sie mit npm run dev
neu), und öffnen sie im Browser. Wir sollten ein Formular sehen. Wir geben einen Benutzernamen und Raumnamen ein und senden das Formular. Die Ansicht ändert sich und wir sehen die von uns ausgewählten Namen sowie das Token, das vom Server abgerufen wurde.
Die Room-Komponente
Nachdem wir der Anwendung jetzt einen Benutzernamen und Raumnamen hinzugefügt haben, können wir mit diesen Namen einem Twilio Video-Chatroom beitreten. Für die Arbeit mit dem Twilio Video-Dienst benötigen wir das JS-SDK. Für dieses Beispiel arbeiten wir mit Twilio Video Version 2.2.0. Wir installieren die Version mit diesem Befehl:
Wir erstellen eine neue Datei im src
-Verzeichnis mit dem Namen Room.js
. Wir beginnen die Datei mit dem folgenden Textbaustein. In dieser Komponente verwenden wir das Twilio Video-SDK sowie die Hooks useState
und useEffect
. Außerdem rufen wir roomName
, token
und handleLogout
als Eigenschaften von der übergeordneten VideoChat
-Komponente ab:
Die Komponente stellt als Erstes anhand des Tokens und des Raumnamens eine Verbindung zum Twilio Video-Dienst her. Sobald die Verbindung hergestellt ist, erhalten wir ein room
-Objekt, das wir speichern. Der Raum enthält außerdem eine Liste mit Teilnehmern. Die Teilnehmer ändern sich im Lauf der Zeit. Deshalb speichern wir auch diese Liste. Dazu verwenden wir useState
, wobei der Anfangswert für den Raum null
ist und für die Teilnehmer ein leeres Array:
Bevor wir schließlich dem Raum beitreten, rendern wir noch etwas für diese Komponente. Wir führen eine Zuordnung für das Teilnehmer-Array aus, um die Identität jedes Teilnehmers sowie die Identität des lokalen Teilnehmers im Raum anzuzeigen:
Wir aktualisieren die VideoChat
-Komponente, um diese Room
-Komponente statt der zuvor verwendeten Platzhalterinformationen zu rendern.
Wenn wir das jetzt im Browser ausführen, werden der Raumname und die Schaltfläche „Abmelden“ angezeigt, aber keine Teilnehmeridentitäten, da wir noch keine Verbindung zum Raum hergestellt haben und ihm noch nicht beigetreten sind.
Wir haben alle erforderlichen Informationen, um dem Raum beizutreten, deshalb sollten wir die Aktion zum Herstellen einer Verbindung beim ersten Rendern der Komponente auslösen. Und sobald die Komponente zerstört ist, möchten wir den Raum verlassen. (Es ergibt keinen Sinn, eine WebRTC-Verbindung im Hintergrund aufrechtzuerhalten.) Das sind beides Nebenwirkungen.
Bei klassenbasierten Komponenten würden wir hier die lifecycle-Methoden componentDidMount
und componentWillUnmount
verwenden. Bei React-Hooks verwenden wir den useEffect-Hook.
useEffect
useEffect
ist eine Funktion, die eine Methode benötigt und diese ausführt, sobald die Komponente gerendert wurde. Wenn die Komponente geladen wird, möchten wir eine Verbindung zum Videodienst herstellen. Außerdem benötigen wir Funktionen, die wir immer dann ausführen können, wenn ein Teilnehmer dem Raum beitritt oder ihn wieder verlässt, um Teilnehmer dem Zustand hinzuzufügen bzw. sie aus dem Zustand zu entfernen.
Beginnen wir mit dem Erstellen des Hooks, indem wir den folgenden Code vor JSX in der Datei Room.js
einfügen:
Dadurch werden token
und roomName
zum Herstellen einer Verbindung zum Twilio Video-Dienst verwendet. Wenn die Verbindung hergestellt wurde, legen wir den Zustand des Raums fest, richten einen Listener für weitere Teilnehmer ein, die eine Verbindung herstellen oder diese trennen, und führen eine Schleife aus, um vorhandene Teilnehmer dem Zustand des Teilnehmer-Arrays hinzuzufügen. Dazu bedienen wir uns der participantConnected
-Funktion, die wir zuvor geschrieben haben.
Das ist zwar ein guter Anfang, aber wenn wir die Komponente entfernen, sind wir immer noch mit dem Raum verbunden. Wir müssen also hinter uns aufräumen.
Wenn wir vom Rückruf, den wir an useEffect
übergeben, eine Funktion zurückgeben, wird diese ausgeführt, wenn die Bereitstellung der Komponente aufgehoben wird. Wenn eine Komponente, die useEffect
verwendet, neu gerendert wird, wird auch diese Funktion aufgerufen, um den Effekt zu bereinigen, bevor die Komponente erneut ausgeführt wird.
Wir geben jetzt eine Funktion zurück, die alle Video- und Audiospuren des lokalen Teilnehmers beendet und dann die Verbindung zum Raum trennt, falls der lokale Teilnehmer verbunden ist:
Wir müssen beachten, dass wir hier die Rückruf-Version der setRoom
-Funktion verwenden, die wir zuvor von useState
abgerufen haben. Wenn wir eine Funktion an setRoom
übergeben, wird diese anhand des vorherigen Werts aufgerufen. In diesem Fall ist das der vorhandene Raum, den wir currentRoom
nennen. Der Zustand wird auf das festgelegt, was wir zurückgeben.
Das ist aber noch nicht alles. Im aktuellen Zustand verlässt diese Komponente den Raum, dem wir beigetreten sind, und stellt jedes Mal erneut eine Verbindung mit ihm her, wenn die Komponente neu gerendert wird. Das ist nicht optimal, deshalb müssen wir der Komponente sagen, wann eine Bereinigung erforderlich ist und der Effekt erneut ausgeführt werden soll. Ähnlich wie bei useCallback
übergeben wir in diesem Fall ein Array mit Variablen, von denen der Effekt abhängig ist. Wenn sich die Variablen geändert haben, möchten wir, dass zuerst eine Bereinigung und dann der Effekt erneut ausgeführt wird. Wenn sie sich nicht geändert haben, muss der Effekt nicht erneut ausgeführt werden.
Wenn wir die Funktion genauer betrachten, dann müssten wir erwarten, dass beim Ändern von roomName
oder token
eine Verbindung zu einem anderen Raum oder als anderer Benutzer hergestellt wird. Wir übergeben diese Variablen also auch als Array an useEffect
:
Wir müssen beachten, dass in diesem Effekt zwei Rückruf-Funktionen definiert sind. Man könnte jetzt vielleicht denken, dass diese wie zuvor mit useCallback
umschlossen werden sollten, das ist aber nicht der Fall. Da sie Teil des Effekts sind, werden sie nur ausgeführt, wenn die Abhängigkeiten aktualisiert werden. Außerdem können wir in Rückruf-Funktionen keine Hooks verwenden. Sie müssen direkt in Komponenten oder einem benutzerdefinierten Hook verwendet werden.
Wir sind jetzt fast durch mit dieser Komponente. Prüfen wir, ob sie soweit auch funktioniert. Wir laden die Anwendung neu und geben einen Benutzernamen und Raumnamen ein. Sobald wir dem Raum beitreten, sollten wir unsere Identität sehen. Wenn wir auf die Schaltfläche „Abmelden“ klicken, gelangen wir in den Wartebereich zurück.
Das letzte Puzzleteil ist das Rendern der Teilnehmer im Videoanruf, indem ihr Video und Audio auf der Webseite hinzugefügt werden.
Die Participant-Komponente
Wir erstellen eine neue Komponente unter src
mit dem Namen Participant.js
. Wir beginnen mit dem üblichen Textbaustein, aber in dieser Komponente verwenden wir drei Hooks: useState
und useEffect
, die uns bereits bekannt sind, und useRef
. Außerdem übergeben wir ein participant
-Objekt in den Eigenschaften und verfolgen die Video- und Audiospur des Teilnehmers mit useState
:
Wenn wir einen Video- oder Audiodatenstrom von unserem Teilnehmer empfangen, fügen wir diesen einem <video>
- oder <audio>
-Element an. Da JSX deklarativ ist, haben wir keinen direkten Zugriff auf das Dokumentobjektmodell (DOM). Deshalb müssen wir uns auf anderem Weg eine Referenz zum HTML-Element besorgen.
React bietet Zugriff auf das DOM über Refs und den useRef
-Hook. Wenn wir Refs verwenden möchten, deklarieren wir sie zuerst, und verweisen dann auf sie innerhalb von JSX. Wir erstellen unsere Refs mithilfe des useRef
-Hooks, bevor wir uns ans Rendern machen:
Wir geben jetzt das gewünschte JSX-Element zurück. Zum Anfügen des JSX-Elements an die Ref verwenden wir das ref
-Attribut.
Außerdem habe ich die Attribute der <video>
- und <audio>
-Tags auf automatische Wiedergabe eingestellt (damit diese wiedergegeben werden, sobald ein Mediadatenstrom empfangen wird) und stummgeschaltet (damit wir uns beim Testen durch Rückkopplungen keinen Gehörschaden zufügen; diesen Fehler mache ich bestimmt nicht noch einmal!).
Diese Komponente an sich hat noch keine Funktion, da wir zuerst noch ein paar Effekte anwenden müssen. Wir werden den useEffect
-Hook sogar dreimal in dieser Komponente verwenden. Warum, das sehen wir gleich.
Der erste useEffect
-Hook legt die Video- und Audiospur im Zustand fest und richtet Listeners für das Participant-Objekt ein, für den Fall, dass Video- und Audiospuren hinzugefügt oder entfernt werden. Wenn die Bereitstellung der Komponente aufgehoben wird, muss er außerdem diese Listeners bereinigen und entfernen und den Zustand leeren.
Unserem ersten useEffect
-Hook fügen wir zwei Funktionen hinzu, die ausgeführt werden, wenn entweder eine Spur zum Teilnehmer hinzugefügt oder vom Teilnehmer entfernt wird. Beide Funktionen prüfen, ob es sich bei der Spur um eine Video- oder Audiospur handelt, und fügen sie dann dem Zustand hinzu oder entfernen sie aus dem Zustand mithilfe der entsprechenden Zustandsfunktion.
Als Nächstes verwenden wir das Participant-Objekt, um die Anfangswerte für die Audio- und Videospuren festzulegen. Teilnehmer verfügen über videoTracks
- und audioTracks
-Eigenschaften, die eine Zuordnung von TrackPublication
-Objekten zurückgeben. Eine TrackPublication
hat erst Zugriff auf ihr track
-Objekt, wenn es abonniert wird. Deshalb müssen wir alle Spuren herausfiltern, die nicht vorhanden sind. Dazu bedienen wir uns einer Funktion, die eine Zuordnung zwischen TrackPublication
s und Track
s durchführt und alle Spuren herausfiltert, die den Wert null
haben.
Anschließend richten wir mit den gerade eben geschriebenen Funktionen Listeners für trackSubscribed
- und trackUnsubscribed
-Ereignisse ein und führen eine Bereinigung mit der zurückgegebenen Funktion durch:
Wir müssen beachten, dass der Hook nur vom participant
-Objekt abhängt und nur dann bereinigt und erneut ausgeführt wird, wenn sich der Teilnehmer ändert.
Außerdem benötigen wir einen useEffect
-Hook, um die Video- und Audiospuren an das DOM anzufügen. Ich gehe hier nur auf die Videospur ein, aber der Vorgang ist der gleiche für Audio. Dabei muss nur Video durch Audio ersetzt werden. Der Hook ruft die erste Videospur vom Zustand ab und fügt sie, sofern vorhanden, an den DOM-Knoten an, den wir zuvor mit einer Ref erfasst haben. Wir können auf den aktuellen DOM-Knoten in der Ref mithilfe von videoRef.current
verweisen. Wenn wir die Videospur anfügen, müssen wir außerdem eine Funktion zurückgeben, mit der die Spur bei der Bereinigung getrennt wird.
Wir wiederholen diesen Hook für audioTracks
, und dann sind wir auch schon bereit, unsere Participant
-Komponente über die Room
-Komponente zu rendern. Wir importieren die Participant
-Komponente am Anfang der Datei und ersetzen die Absätze, die die Identität angezeigt haben, durch die Komponente selbst.
Wir laden die App jetzt neu, treten einem Raum bei und wir werden uns selbst auf dem Bildschirm sehen. Wir öffnen einen weiteren Browser, treten demselben Raum bei und wir werden uns zweimal sehen. Wenn wir auf die Schaltfläche „Abmelden“ klicken, gelangen wir in den Wartebereich zurück.
Fazit
Das Erstellen mit Twilio Video in React erfordert etwas Mehraufwand, da es eine Vielzahl von Nebenwirkungen gibt, die beachtet werden müssen. Von der Anforderung des Tokens über das Herstellen einer Verbindung zum Videodienst bis hin zum Bearbeiten des DOM, um <video>
- und <audio>
-Elemente zu verbinden: Es gibt einiges, auf das wir uns einstellen müssen. In diesem Blogbeitrag haben wir gesehen, wie useState
, useCallback
, useEffect
und useRef
verwendet werden, um diese Nebenwirkungen zu kontrollieren und unsere App nur mit Funktionskomponenten zu erstellen.
Ich hoffe, dass dieser Blogbeitrag zum besseren Verständnis von Twilio Video und React-Hooks beiträgt. Der gesamte Quellcode dieser Anwendung ist auf GitHub verfügbar. Du kannst ihn in seine Einzelteile zerlegen und wieder zusammensetzen.
Weitere Informationen zu React-Hooks findest du in der sehr detaillierten offiziellen Dokumentation sowie in dieser Veranschaulichung unter „Thinking in React Hooks“ und in Dan Abramovs Tiefenanalyse zu useEffect (ein langer Post, bei dem sich das Lesen lohnt, versprochen).
Wenn du mehr über das Entwickeln mit Twilio Video erfahren möchtest, dann sieh dir die folgenden Posts an: Kamerawechsel während eines Videochats mit Twilio Video und Hinzufügen der Bildschirmfreigabe zum Videochat.
Wenn du diese oder andere coole Videochat-Funktionen in React erstellst, möchte ich gern davon erfahren. Hinterlasse einfach hier oder auf Twitter einen Kommentar oder sende eine E-Mail an philnash@twilio.com.
Verwandte Posts
Ähnliche Ressourcen
Twilio Docs
Von APIs über SDKs bis hin zu Beispiel-Apps
API-Referenzdokumentation, SDKs, Hilfsbibliotheken, Schnellstarts und Tutorials für Ihre Sprache und Plattform.
Ressourcen-Center
Die neuesten E-Books, Branchenberichte und Webinare
Lernen Sie von Customer-Engagement-Experten, um Ihre eigene Kommunikation zu verbessern.
Ahoy
Twilios Entwickler-Community-Hub
Best Practices, Codebeispiele und Inspiration zum Aufbau von Kommunikations- und digitalen Interaktionserlebnissen.