Создайте приложение для видеочата с помощью Twilio Video и React, используя только функциональные компоненты.
Мы видели видеочат, построенный с помощью React в блоге Twilio, но с тех пор, в версии 16.8, React выпустил Hooks. Хуки позволяют использовать состояние или другие функции React внутри функциональных компонентов вместо написания компонента класса.
В этом посте мы собираемся создать приложение для видеочата с использованием Twilio Video и React только с функциональными компонентами, используя хуки useState
, useCallback
, useEffect
и useRef
.
Что вам понадобится
Чтобы создать это приложение для видеочата, вам понадобится следующее:
- Установлены Node.js и npm.
- Учетная запись Twilio (зарегистрируйте бесплатную учетную запись Twilio здесь).
Как только у вас будет все это, мы сможем подготовить нашу среду разработки.
Начиная
Итак, мы можем сразу перейти к приложению React, мы можем начать с начального приложения React and Express, созданного мной. Загрузите или клонируйте ветвь twilio начального приложения, перейдите в новый каталог и установите зависимости:
git clone -b twilio [email protected]: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, вы можете создать их в Программируемых видео инструментах в вашей консоли. Создайте пару ключей и добавьте SID и секрет как TWILIO_API_KEY
и TWILIO_API_SECRET
в файл .env
.
Добавление стиля
В этом посте мы не будем касаться CSS, но давайте добавим немного, чтобы результат не выглядел ужасно! Возьмите CSS из этого URL и замените им содержимое src/App.css
.
Теперь мы готовы приступить к сборке.
Планирование наших компонентов
Все начнется с нашего App
компонента, где мы можем разместить верхний и нижний колонтитулы для приложения, а также VideoChat
компонент.
Внутри компонента VideoChat
мы хотим показать компонент Lobby
, в котором пользователь может ввести свое имя и комнату, в которую он хочет присоединиться.
После того, как они введут эти данные, мы заменим Lobby
компонентом Room
, который будет обеспечивать подключение к комнате и отображение участников видеочата.
Наконец, для каждого участника в комнате мы визуализируем Participant
компонент, который будет обрабатывать отображение их мультимедиа.
Сборка компонентов
Компонент приложения
Откройте src/App.js
, здесь много кода из исходного примера приложения, который мы можем удалить.
Кроме того, компонент App
- это компонент на основе классов. Мы сказали, что создадим все приложение с функциональными компонентами, поэтому лучше это изменить.
Из импорта удалите Component
и импорт logo.svg
. Замените весь класс 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
и setState
. Мы собираемся использовать setState
для отслеживания имени пользователя, имени комнаты и токена в нашем компоненте.
Начните с импорта useState
из React и настройте состояния для имени пользователя, имени комнаты и токена:
Затем нам нужны две функции для обработки обновления username
и roomName
, когда пользователь вводит их в соответствующие элементы ввода.
Хотя это будет работать, мы можем оптимизировать наш компонент, используя здесь еще один React Hook; useCallback
.
useCallback
Каждый раз, когда вызывается этот функциональный компонент, handleXXX
функции переопределяются. Они должны быть частью компонента, потому что полагаются на функции setUsername
и setRoomName
, но каждый раз они будут одинаковыми.
useCallback
- это React Hook, который позволяет нам запоминать функции. То есть, если они одинаковы между вызовами функций, они не будут переопределены.
useCallback
принимает два аргумента: функцию, которая должна быть запомнена, и массив зависимостей функции. Если какая-либо из зависимостей функции изменяется, это означает, что мемоизированная функция устарела, а затем функция переопределяется и снова запоминается.
В этом случае между этими двумя функциями нет зависимостей, поэтому будет достаточно пустого массива (setState
функции из useState
Hook считаются постоянными внутри функции).
Переписывая эту функцию, нам нужно добавить useCallback
к импорту в верхней части файла, а затем обернуть каждую из этих функций.
Когда пользователь отправляет форму, мы хотим отправить имя пользователя и имя комнаты на сервер для обмена на токен доступа, который мы можем использовать для входа в комнату. Мы также создадим эту функцию в этом компоненте.
Мы будем использовать Fetch API, чтобы отправить данные в виде JSON на конечную точку, получить и проанализировать ответ, а затем использовать setToken
для сохранения токена в нашем состоянии.
Мы также заключим эту функцию в useCallback
, но в этом случае функция будет зависеть от username
и roomName
, поэтому мы добавляем их как зависимости к useCallback
.
Для последней функции в этом компоненте мы добавим функцию выхода из системы. Это вытолкнет пользователя из комнаты и вернет его в вестибюль. Для этого мы установим токен на null
. Еще раз, мы завершаем это в useCallback
без каких-либо зависимостей.
const handleLogout = useCallback(event => { setToken(null); }, []); return <div></div> // we'll build up our response later };
Этот компонент в основном управляет компонентами, находящимися под ним, поэтому отрисовывать особо нечего, пока мы не создадим эти компоненты.
Давайте создадим компонент Lobby
, который отображает форму, которая запрашивает имя пользователя и название комнаты.
Компонент Лобби
Основная задача компонента Lobby
- визуализировать форму с использованием этих свойств, например:
Давайте обновим компонент VideoChat
, чтобы отобразить Lobby
, если у нас нет token
, иначе мы будем отображать username
, roomName
и token
.
Нам нужно будет импортировать Lobby
компонент в верхней части файла и отобразить JSX в нижней части функции компонента:
Чтобы это отображалось на странице, нам также необходимо импортировать компонент VideoChat
в компонент App
и отрендерить его. Снова откройте src/App.js
и внесите следующие изменения:
Убедитесь, что приложение все еще работает (или перезапустите его с помощью npm run dev
), откройте его в браузере, и вы увидите форму.
Введите имя пользователя и название комнаты и отправьте запрос, и вид изменится, чтобы показать вам выбранные вами имена и токен, полученный с сервера.
Компонент "Комната"
Теперь, когда мы добавили в приложение имя пользователя и название комнаты, мы можем использовать их для присоединения к комнате чата Twilio Video. Для работы со службой Twilio Video нам понадобится JS SDK, установите его с помощью:
npm install twilio-video --save
Создайте новый файл в каталоге src
с именем Room.js
. Начните со следующего шаблона.
В этом компоненте мы будем использовать Twilio Video SDK, а также хуки useState
и useEffect
. Мы также получим roomName
, token
и handleLogout
в качестве свойств родительского компонента VideoChat
:
import React, { useState, useEffect } from 'react'; import Video from 'twilio-video'; const Room = ({ roomName, token, handleLogout }) => { }); export default Room;
Первое, что сделает компонент, это подключится к сервису Twilio Video, используя токен и roomName
. При подключении мы получим room
объект, который захотим сохранить.
В комнате также есть список участников, который со временем будет меняться, поэтому мы его тоже будем хранить. Мы будем использовать useState
для их хранения, начальные значения будут null
для комнаты и пустой массив для участников:
const Room = ({ roomName, token, handleLogout }) => { const [room, setRoom] = useState(null); const [participants, setParticipants] = useState([]); });
Прежде чем мы перейдем к объединению с комнатой, давайте что-нибудь отрендерим для этого компонента. Мы нанесем карту на массив участников, чтобы показать личность каждого участника, а также показать личность местного участника в комнате:
Давайте обновим компонент VideoChat
, чтобы отобразить этот компонент Room
вместо информации-заполнителя, которая у нас была ранее.
Запустив это в браузере, вы увидите название комнаты и кнопку выхода, но не идентификаторы участников, потому что мы еще не подключились и не присоединились к комнате.
У нас есть вся информация, необходимая для присоединения к комнате, поэтому мы должны активировать действие для подключения при первом рендеринге компонента.
Мы также хотим выйти из комнаты после того, как компонент будет уничтожен (нет смысла поддерживать соединение WebRTC в фоновом режиме). Это оба побочных эффекта.
С компонентами на основе классов здесь вы можете использовать методы жизненного цикла componentDidMount
и componentWillUnmount
. В React Hooks мы будем использовать useEffect Hook.
useEffect
useEffect
- это функция, которая принимает метод и запускает его после рендеринга компонента.
Когда наш компонент загружается, мы хотим подключиться к видеосервису, нам также потребуются функции, которые мы можем запускать всякий раз, когда участник присоединяется или покидает комнату, чтобы добавлять и удалять участников из состояния соответственно.
Давайте начнем создавать наш Hook, добавив этот код перед JSX в Room.js
:
При этом используются token
и roomName
для подключения к сервису Twilio Video.
Когда соединение завершено, мы устанавливаем состояние комнаты, настраиваем прослушиватель для других участников, подключающихся или отключающихся, и перебираем всех существующих участников, добавляя их в состояние массива участников с помощью функции participantConnected
, которую мы написали ранее.
Это хорошее начало, но если мы удалим компонент, мы все равно будем связаны с комнатой. Итак, нам нужно убирать и за собой.
Если мы вернем функцию из обратного вызова, который мы передаем useEffect
, она будет запущена, когда компонент будет размонтирован. Когда компонент, использующий useEffect
, повторно визуализируется, эта функция также вызывается для очистки эффекта перед его повторным запуском.
Давайте вернем функцию, которая останавливает все треки локального участника и затем отключается от комнаты, если локальный участник подключен:
Обратите внимание, что здесь мы используем версию обратного вызова функции setRoom
, которую мы получили от useState
ранее.
Если вы передадите функцию в setRoom
, она будет вызываться с предыдущим значением, в данном случае с существующей комнатой, которую мы назовем currentRoom
, и она установит состояние, которое вы вернете.
Но мы еще не закончили. В своем текущем состоянии этот компонент будет выходить из объединенной комнаты и повторно подключаться к ней каждый раз при повторной визуализации. Это не идеально, поэтому нам нужно указать, когда нужно очистить и запустить эффект снова.
Как и useCallback
, мы делаем это, передавая массив переменных, от которых зависит эффект. Если переменные изменились, мы хотим сначала очистить, а затем снова запустить эффект. Если они не изменились, нет необходимости запускать эффект снова.
Глядя на функцию, мы видим, что если бы roomName
или token
изменились, мы ожидали бы подключения к другой комнате или в качестве другого пользователя. Давайте также передадим эти переменные в виде массива в useEffect
:
Обратите внимание, что в этом эффекте определены две функции обратного вызова. Вы можете подумать, что их следует обернуть в useCallback
, как мы делали раньше, но это не так.
Поскольку они являются частью эффекта, они будут запускаться только при обновлении зависимостей. Вы также не можете использовать хуки в функциях обратного вызова, они должны использоваться непосредственно в компонентах или в пользовательском хуке.
По большей части мы закончили с этим компонентом. Давайте проверим, все ли работает, перезагрузим приложение и введем имя пользователя и название комнаты.
Вы должны увидеть свою личность, когда войдете в комнату. Нажав кнопку выхода, вы вернетесь в лобби.
Последний кусок головоломки - отрендерить участников видеозвонка, добавив их видео и аудио на страницу.
Компонент участника
Создайте новый компонент в src
под названием Participant.js
. Мы начнем с обычного шаблона, хотя в этом компоненте мы собираемся использовать три обработчика: useState
и useEffect
, которые мы видели, и useRef
.
Мы также будем передавать participant
объект в props и отслеживать видео- и аудиодорожки участника с помощью useState
:
Когда мы получаем от участника видеопоток или аудиопоток, нам нужно прикрепить его к элементу <video>
или <audio>
.
Поскольку JSX декларативен, мы не получаем прямого доступа к DOM (объектной модели документа), поэтому нам нужно получить ссылку на элемент HTML каким-либо другим способом.
React предоставляет доступ к DOM через refs и useRef Hook. Чтобы использовать ссылки, мы объявляем их заранее, а затем ссылаемся на них в JSX. Мы создаем наши ссылки с помощью useRef
Hook, прежде чем что-либо отрендерить:
А пока давайте вернем JSX, который нам нужен. Чтобы подключить элемент JSX к ссылке, мы используем атрибут ref
.
Я также установил атрибуты тегов <video>
и <audio>
на автовоспроизведение (чтобы они воспроизводились, как только у них есть поток мультимедиа) и без звука (чтобы я не оглушал себя отзывами во время тестирования, вы меня благодарите за это, если вы когда-нибудь совершите эту ошибку).
Этот компонент пока мало что делает, так как нам нужно использовать некоторые эффекты. На самом деле мы будем использовать useEffect
Hook в этом компоненте трижды, скоро вы поймете, почему.
Первый useEffect
Hook установит видео- и аудиодорожки в состояние и настроит слушателей для объекта-участника, когда треки добавляются или удаляются. Также потребуется очистить и удалить эти прослушиватели и очистить состояние, когда компонент отключен.
В наш первый useEffect
Hook мы добавим две функции, которые будут запускаться либо при добавлении дорожки, либо при удалении ее из участника.
Эти функции проверяют, является ли дорожка звуковой или видеодорожкой, а затем добавляют или удаляют ее из состояния с помощью соответствующей функции состояния.
Затем мы используем объект participant
для установки начальных значений для аудио- и видеодорожек, настраиваем слушателей для событий trackSubscribed
и trackUnsubscribed
с помощью только что написанных функций, а затем выполняем очистку в возвращенной функции:
Обратите внимание, что ловушка зависит только от объекта participant
и не будет очищена и запущена повторно, если участник не изменится.
Нам также понадобится useEffect
Hook, чтобы прикрепить видео- и аудиодорожки к DOM. Я покажу только одну из них, видео-версию, но звук останется таким же, если вы замените аудио на видео.
Хук получит первую видеодорожку из состояния и, если она существует, присоединит ее к узлу DOM, который мы захватили с помощью ссылки ранее. Вы можете ссылаться на текущий узел DOM в ссылке, используя videoRef.current
.
Если мы прикрепим видеодорожку, нам также нужно будет вернуть функцию, чтобы отсоединить ее во время очистки.
Повторите эту ловушку для audioTracks
, и мы готовы визуализировать наш Participant
компонент из Room
компонента.
Импортируйте компонент Participant
в начало файла, а затем замените абзацы, в которых отображается идентичность, на сам компонент.
Теперь перезагрузите приложение, присоединитесь к комнате, и вы увидите себя на экране. Откройте другой браузер и войдите в ту же комнату, и вы увидите себя дважды. Нажмите кнопку выхода, и вы вернетесь в холл.
Заключение
Сборка с помощью Twilio Video в React требует немного больше работы, потому что приходится иметь дело со всевозможными побочными эффектами.
От запроса на получение токена, подключения к видеосервису и манипулирования DOM для соединения элементов <video>
и <audio>
, есть немало вещей, которые нужно разобраться.
В этом посте мы увидели, как использовать useState
, useCallback
, useEffect
и useRef
для управления этими побочными эффектами и создания нашего приложения, используя только функциональные компоненты.
Надеюсь, это поможет вам понять как Twilio Video, так и React Hooks. Весь исходный код этого приложения доступен на GitHub, чтобы вы могли разобрать его и собрать.
Для дальнейшего чтения по React Hooks взгляните на официальную документацию, которая очень подробно описывает эту визуализацию мышления с помощью хуков, и ознакомьтесь с глубоким погружением Дэна Абрамова в useEffect
(это длинный пост, но того стоит, обещаю).
Если вы хотите узнать больше о создании с помощью Twilio Video, ознакомьтесь с этими сообщениями о переключении камер во время видеочата или добавлении совместного использования экрана в видеочате.
Если вы создадите эти или другие интересные функции видеочата в React, дайте мне знать в комментариях.