분류 Reactjs

React Hooks로 화상 채팅 만들기

컨텐츠 정보

  • 조회 712 (작성일 )

본문

Build a Video Chat with React Hooks 


우리는 이 블로그에서 React에 내장 된 화상 채팅을 보았지만 그 이후 버전 16.8에서 React가 Hooks를 발표했습니다. 후크를 사용하면 클래스 구성 요소를 작성하는 대신 함수 구성 요소 내에서 상태 또는 기타 React 기능을 사용할 수 있습니다.

이 글에서는 useState, useCallback, useEffect 및 useRef 후크를 사용하여 Twilio Video 및 React를 함수 컴포넌트 만 사용하여 화상 채팅 애플리케이션을 빌드합니다.


https://www.twilio.com/blog/video-chat-react-hooks 


필요한 것 


이 화상 채팅 응용 프로그램을 구축하려면 다음이 필요합니다.

모든 것이 준비되면 개발 환경을 준비 할 수 있습니다.


시작하기 


그래서 우리는 React 어플리케이션으로 바로 갈 수 있고, 우리가 만든 React and Express 스타터 앱으로 시작할 수 있습니다. 스타터 앱의 "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 


브라우저에이 페이지가 로드 되어 있어야 합니다.


The initial page shows the React logo and a form 

Twilio 자격 증명 준비 


Twilio 비디오에 연결하려면 몇 가지 자격 증명이 필요합니다. Twilio 콘솔에서 계정 SID를 복사하여 .env 파일에 TWILIO_ACCOUNT_SID로 입력하십시오.


또한 API 키와 비밀이 필요하며 콘솔의 프로그래밍 가능한 비디오 도구에서 생성 할 수 있습니다. 키 페어를 생성하고 SID 및 Secret을 TWILIO_API_KEY 및 TWILIO_API_SECRET으로 .env 파일에 추가하십시오.


스타일 추가 


이 게시물의 CSS에 대해서는 신경 쓰지 않겠지만 결과가 끔찍해지지 않도록 몇 가지를 추가하겠습니다! 이 URL에서 CSS를 가져 와서 src / App.css의 내용을 바꾸십시오.


이제 건물을 시작할 준비가 되었습니다.


컴포넌트 계획 


모든 것이 앱 구성 요소에서 시작하여 VideoChat 구성 요소 뿐만 아니라 앱의 머리글과 바닥 글을 배치 할 수 있습니다. VideoChat 구성 요소 내에서 사용자는 이름과 참여할 방을 입력 할 수 있는 로비 구성 요소를 표시하려고 합니다. 세부 정보를 입력 한 후에는 로비를 회의실에 연결하고 화상 채팅에 참가자를 표시합니다. 마지막으로 회의실의 각 참가자에 대해 미디어 표시를 처리하는 참가자 구성 요소를 렌더링 합니다.


컴포넌트 구축 


앱 구성 요소 


src/App.js를 여십시오. 여기서 우리가 제거 할 수 있는 초기 예제 앱의 코드가 많이 있습니다. 또한 앱 구성 요소는 클래스 기반 구성 요소입니다. 우리는 함수형 구성 요소를 사용하여 전체 앱을 만들 것이라고 말하면서 더 잘 변경했습니다.


가져 오기에서 Component 및 logo.svg 가져 오기를 제거하십시오. 전체 앱 클래스를 애플리케이션 스켈레톤을 렌더링 하는 함수로 바꿉니다. 전체 파일은 다음과 같아야 합니다.


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와 setState와 같은 두 가지 고유 한 변수를 제공하기 위해 해당 배열을 변형합니다. 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 업데이트를 처리하는 두 가지 기능이 필요합니다.


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
};

이것이 작동하는 동안 다른 React 후크를 사용하여 컴포넌트를 최적화 할 수 있습니다. useCallback


useCallback 


이 함수 컴포넌트가 호출 될 때마다 handleXXX 함수가 재정의 됩니다. setUsername 및 setRoomName 함수를 사용하므로 구성 요소의 일부 여야 하지만 매번 동일합니다. useCallback은 React hook으로 함수를 메모 할 수 있습니다. 즉, 함수 호출간에 동일하면 재정의 되지 않습니다.


useCallback은 메모 할 함수와 함수의 종속성 배열 인 두 개의 인수를 사용합니다. 함수의 종속성이 변경되면 메모 된 함수가 오래되었고 함수가 재정의 되고 다시 메모 됩니다.


이 경우 이 두 함수에 대한 종속성이 없으므로 빈 배열로 충분합니다 (useState 후크의 setState 함수는 함수 내에서 일정하다고 간주됩니다). 이 함수를 다시 작성하려면 파일 맨 위에 가져 오기에 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을 사용하여 상태에 토큰을 저장합니다. 이 함수도 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 컴포넌트를 작성해 봅시다.


로비 구성 요소 


src/Lobby.js에 새 파일을 작성하십시오. 이 구성 요소는 모든 이벤트를 상위 이벤트 인 VideoChat 구성 요소로 전달하므로 데이터를 저장할 필요가 없습니다. 구성 요소가 렌더링 되면 사용자 이름 및 roomName과 각 변경 사항을 처리하고 양식 제출을 처리하는 함수가 전달됩니다. 나중에 사용하기 쉽도록 소품을 구조화 할 수 있습니다.


Lobby 구성 요소의 주요 작업은 다음과 같은 소품을 사용하여 양식을 렌더링 하는 것입니다.


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 구성 요소를 업데이트하고, 그렇지 않으면 사용자 이름, roomName 및 토큰을 렌더링 합니다. 파일 맨 위에서 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로 다시 시작) 브라우저에서 열면 양식이 표시됩니다. 사용자 이름과 회의실 이름을 입력하고 제출하면 선택한 이름과 서버에서 검색된 토큰이 표시되도록 보기가 변경됩니다.


Fill in the form form and submit and you will see the username, room name and token on the page. 

방 구성 요소 


응용 프로그램에 사용자 이름과 방 이름을 추가 했으므로 이를 사용하여 Twilio 비디오 대화방에 참여할 수 있습니다. Twilio Video 서비스를 사용하려면 JS SDK가 필요합니다.


npm install twilio-video --save 


Room.js라는 src 디렉토리에 새 파일을 작성하십시오. 다음 상용구로 시작하십시오. 우리는 useState 및 useEffect 훅뿐만 아니라 이 컴포넌트에서 Twilio Video SDK를 사용할 것입니다. 부모 VideoChat 구성 요소에서 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 서비스에 연결하는 것입니다. 연결하면 저장하려는 룸 객체가 생깁니다. 이 방에는 시간이 지남에 따라 변경 될 참가자 목록도 포함되어 있으므로 함께 보관할 것입니다. useState를 사용하여 이를 저장합니다. 회의실의 초기 값은 null이고 참가자의 빈 배열은 다음과 같습니다.


const Room = ({ roomName, token, handleLogout }) => {
  const [room, setRoom] = useState(null);
  const [participants, setParticipants] = useState([]);
});

회의실에 참여하기 전에 이 구성 요소를 위해 무언가를 렌더링 해 보겠습니다. 참가자 배열을 매핑하여 각 참가자의 신원을 표시하고 회의실의 로컬 참가자의 신원도 표시합니다.


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;
};

브라우저에서 이것을 실행하면 회의실 이름과 로그 아웃 버튼이 표시되지만 회의실에 아직 연결되지 않았고 참가하지 않았기 때문에 참가자 ID가 없습니다.


When you submit the form now, you will see the room name, "Awesome Room" in this case, and a space for remote participants. 


회의실에 참여하는 데 필요한 모든 정보가 있으므로 구성 요소의 첫 번째 렌더링에서 연결되도록 작업을 트리거해야 합니다. 또한 구성 요소가 파괴되면 방을 나가고 싶습니다 (백그라운드에서 WebRTC 연결을 유지하는 지점이 없음). 이것들은 부작용입니다.


클래스 기반 컴포넌트의 경우 componentDidMount 및 componentWillUnmount 라이프 사이클 메소드를 사용합니다. React 후크를 사용하면 useEffect 후크를 사용하게 됩니다.


useEffect 


useEffect는 구성 요소가 렌더링 되면 메서드를 가져 와서 실행하는 함수입니다. 컴포넌트를 로드 할 때 비디오 서비스에 연결하려는 경우 참가자가 참여하거나 회의실을 떠날 때마다 각 주에서 참가자를 추가 및 제거 할 수 있는 기능도 필요합니다.


Room.js의 JSX 앞에이 코드를 추가하여 후크를 구축해 봅시다.


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);
    });
  });

토큰과 roomName을 사용하여 Twilio Video 서비스에 연결합니다. 연결이 완료되면 회의실 상태를 설정하고 연결하거나 연결을 끊은 다른 참가자에 대한 리스너를 설정하고 기존 참가자를 통해 반복하여 앞서 작성한 participantConnected 함수를 사용하여 참가자 배열 상태에 추가합니다.


이것은 좋은 시작이지만 구성 요소를 제거하면 여전히 방에 연결됩니다. 따라서 우리도 스스로를 정리해야 합니다.


콜백에서 함수를 반환하면 useEffect에 전달되며 구성 요소를 마운트 해제 할 때 실행됩니다. useEffect를 사용하는 구성 요소를 다시 렌더링 하면 이 기능이 다시 실행되기 전에 효과를 정리하기 위해 호출됩니다.


우리가 연결되어 있다면 방에서 연결을 끊는 함수를 반환합시다.


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이라고하는 기존 룸과 함께 호출되어 상태를 반환하는 모든 상태로 설정합니다.


아직 끝나지 않았습니다. 현재 상태에서 이 구성 요소는 결합 된 회의실을 종료하고 다시 렌더링 할 때마다 다시 연결됩니다. 이것은 이상적이지 않으므로 효과를 정리하고 다시 실행할 시기를 알려야 합니다. useCallback과 마찬가지로 효과에 의존하는 변수 배열을 전달하여 이 작업을 수행합니다. 변수가 변경된 경우 먼저 정리하고 효과를 다시 실행하려고 합니다. 변경되지 않은 경우 효과를 다시 실행할 필요가 없습니다.


이 함수를 살펴보면 다른 방이나 다른 사용자로 연결될 것으로 예상되는 roomName 또는 token이라는 것을 알 수 있습니다. 이 변수를 배열로 전달하여 효과도 사용하십시오.


    return () => {
      setRoom(currentRoom => {
        if (currentRoom && currentRoom.localParticipant.state === 'connected') {
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  }, [roomName, token]);

이 효과에는 두 개의 콜백 함수가 정의되어 있습니다. 앞에서와 같이 useCallback으로 래핑해야 한다고 생각할 수도 있지만 그렇지 않습니다. 효과의 일부이므로 종속성이 업데이트 될 때만 실행됩니다. 또한 콜백 함수 내에서 후크를 사용할 수 없으며 구성 요소 또는 사용자 정의 후크 내에서 직접 사용해야 합니다.


우리는 대부분이 구성 요소로 수행됩니다. 지금까지 작동하는지 확인하고 응용 프로그램을 다시 로드하고 사용자 이름과 방 이름을 입력하십시오. 회의실에 입장하면 신분이 표시됩니다. 로그 아웃 버튼을 클릭하면 로비로 돌아갑니다.


Now when you submit the form you will see everything before, plus your own identity. 

퍼즐의 마지막 조각은 화상 통화 참가자를 렌더링하여 비디오와 오디오를 페이지에 추가하는 것입니다.


참가자 구성 요소 


src에 Participant.js라는 새 컴포넌트를 작성하십시오. 우리는 일반적인 상용구부터 시작할 것입니다.이 컴포넌트에서는 우리가 본 3 가지 후크, useState와 useEffect, useRef를 사용할 것입니다. 또한 소품에 참가자 객체를 전달하고 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는 선언적이므로 DOM (Document Object Model)에 직접 액세스 할 수 없으므로 다른 방법으로 HTML 요소에 대한 참조를 가져와야 합니다.


React는 refsuseRef 후크를 통해 DOM에 대한 액세스를 제공합니다. 심판을 사용하려면 먼저 선언하고 JSX 내에서 참조하십시오. 우리는 useRef 후크를 사용하여 ref를 생성합니다.


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> 태그의 속성을 자동 재생 (미디어 스트림이 있는 즉시 재생하도록)하고 음소거 (테스트 중에 피드백으로 자신을 보호하지 않도록)으로 설정했습니다. 이 실수를 한다면 이것에 대해 감사하겠습니다)


이 구성 요소는 몇 가지 효과를 사용해야 하므로 아직 많이 하지 않습니다. 우리는 실제로 이 컴포넌트에서 useEffect 훅을 세 번 사용할 것입니다. 왜 그런지 알게 될 것입니다.


첫 번째 useEffect 후크는 상태의 비디오 및 오디오 트랙을 설정하고 트랙을 추가하거나 제거 할 때 리스너를 참가자 객체로 설정합니다. 또한 컴포넌트를 마운트 해제 할 때 해당 리스너를 정리 및 제거하고 상태를 비워야 합니다.


첫 번째 useEffect 후크에는 트랙이 참가자에서 추가되거나 제거 될 때 실행되는 두 가지 기능이 추가됩니다. 이 기능들은 트랙이 오디오 또는 비디오 트랙인지 확인한 다음 관련 상태 기능을 사용하여 상태에서 트랙을 추가 또는 제거합니다.


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

다음으로 참가자 객체를 사용하여 오디오 및 비디오 트랙의 초기 값을 설정하고 방금 작성한 함수를 사용하여 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">

후크는 참가자 객체에만 의존하며 참가자가 변경되지 않는 한 정리 및 재실행 되지 않습니다.


비디오 및 오디오 트랙을 DOM에 연결하려면 useEffect 후크가 필요합니다. 여기에 비디오 버전 중 하나만 표시하겠습니다. 그러나 비디오를 오디오로 대체하면 오디오는 동일합니다. 후크는 상태에서 첫 번째 비디오 트랙을 가져오고, 존재하는 경우 이전에 참조로 캡처 한 DOM 노드에 연결합니다. videoRef.current를 사용하여 심판에서 현재 DOM 노드를 참조 할 수 있습니다. 비디오 트랙을 연결하면 정리하는 동안 분리 할 기능도 반환해야 합니다.


  }, [participant]);

  useEffect(() => {
    const videoTrack = videoTracks[0];
    if (videoTrack) {
      videoTrack.attach(videoRef.current);
      return () => {
        videoTrack.detach();
      };
    }
  }, [videoTracks]);

  return (
    <div className="participant">

'audioTracks'에 대해 후크를 반복하면 'Room'컴포넌트에서 'Participant'컴포넌트를 렌더링 할 준비가 되었습니다. 파일 맨 위에 있는 '참가자'구성 요소를 가져온 다음 ID를 표시 한 단락을 구성 요소 자체로 바꾸십시오.


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>
  );
});

이제 앱을 새로 고침하고 방에 가입하면 화면에 자신이 나타납니다. 다른 브라우저를 열고 같은 방에 참여하면 자신을 두 번 볼 수 있습니다. 로그 아웃 버튼을 누르면 로비로 돌아옵니다.


Success! You should now see yourself in a video chat with yourself. 

결론 


React에서 Twilio Video로 빌드 하는 데에는 모든 종류의 부작용이 있으므로 약간의 작업이 더 필요합니다. 토큰 가져 오기 요청, 비디오 서비스 연결 및 DOM을 조작하여 <video> 및 <audio> 요소를 연결하는 것부터 약간의 여유가 있습니다. 이 게시물에서는 useState, useCallback, useEffect 및 useRef를 사용하여 이러한 부작용을 제어하고 기능적 구성 요소 만 사용하여 앱을 빌드 하는 방법을 살펴 보았습니다.


Twilio Video와 React Hooks에 대한 이해를 돕기를 바랍니다. 이 응용 프로그램의 모든 소스 코드는 GitHub에서 구할 수 있습니다.


React Hooks에 대한 자세한 내용은 공식 문서를 살펴보십시오. 매우 철저한 공식 문서를 살펴보고 후크에 대한 생각에 대한 시각화는 Dan Abramov의 useEffect에 대한 심층 분석을 확인하십시오 (긴 게시물이지만 가치가 있습니다.).


Twilio Video를 사용하여 빌드하는 방법에 대한 자세한 내용을 보려면 비디오 채팅 중 카메라를 전환하거나 비디오 채팅에 화면 공유를 추가하는 방법에 대한 다음 게시물을 확인하십시오.