使用 React Hooks 构建视频聊天

2025-05-28

使用 React Hooks 构建视频聊天

我们之前在这篇博客上看到过用 React 内置的视频聊天功能,但从那以后,在 16.8 版本中,React 发布了Hooks。Hooks 允许你在函数组件中使用状态或其他 React 特性,而无需编写类组件。

在这篇文章中,我们将使用Twilio Video和 React 构建一个视频聊天应用程序,该应用程序仅包含功能组件,使用useStateuseCallback钩子。useEffectuseRef

你需要什么

要构建此视频聊天应用程序,您将需要以下内容:

一旦你掌握了所有这些,我们就可以准备我们的开发环境。

入门

因此,我们可以直接进入 React 应用程序,我们可以从我创建的React 和 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
Enter fullscreen mode Exit fullscreen mode

将文件复制.env.example.env

cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

运行应用程序以确保一切按预期工作:

npm run dev
Enter fullscreen mode Exit fullscreen mode

您应该会在浏览器中看到此页面加载:

初始页面显示 React 徽标和表单

准备 Twilio 凭证

要连接到 Twilio 视频,我们需要一些凭证。从你的Twilio 控制台复制你的帐户 SID,并将其.env作为TWILIO_ACCOUNT_SID.

您还需要一个 API 密钥和密码,您可以在控制台中的可编程视频工具下创建它们。创建一个密钥对,并将 SID 和密码作为TWILIO_API_KEY和添加到文件TWILIO_API_SECRET.env

添加一些样式

这篇文章我们不会关注 CSS,但为了避免效果太差,我们还是添加一些 CSS 吧!从这个 URL 获取 CSS,然后替换 的内容src/App.css

现在我们准备开始建造了。

规划我们的组件

一切从我们的组件开始App,我们可以在其中布局应用的页眉和页脚以及一个VideoChat组件。在VideoChat组件中,我们将显示一个Lobby组件,用户可以在其中输入他们的姓名和想要加入的房间。输入完这些信息后,我们将Lobby用一个Room组件替换它,该组件负责连接房间并显示视频聊天中的参与者。最后,我们将为房间中的每个参与者渲染一个Participant组件,用于显示他们的媒体内容。

构建组件

App 组件

打开src/App.js,这里有很多来自初始示例应用的代码,我们可以删除。另外,该App组件是一个基于类的组件。我们说过要用函数式组件构建整个应用,所以最好改变一下。

从导入中删除Componentlogo.svg 文件。将整个 App 类替换为一个渲染应用程序骨架的函数。整个文件应该如下所示:

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;
Enter fullscreen mode Exit fullscreen mode

VideoChat 组件

此组件将根据用户是否输入用户名和房间名称来显示大厅或房间。创建一个新的组件文件src/VideoChat.js,并使用以下样板文件启动它:

import React from 'react';

const VideoChat = () => {
  return <div></div> // we'll build up our response later
};

export default VideoChat;
Enter fullscreen mode Exit fullscreen mode

VideoChat组件将成为处理聊天数据的顶级组件。我们需要存储加入聊天的用户的用户名、他们将要连接的房间名称,以及从服务器获取的访问令牌。我们将在下一个组件中构建一个表单来输入这些数据。

使用 React Hooks 我们使用useState钩子来存储这些数据。

useState

useState是一个函数,它接受一个参数,即初始状态,并返回一个包含当前状态和更新该状态的函数的数组。我们将解构该数组,得到两个不同的变量,例如statesetState。我们将用它们来setState跟踪组件中的用户名、房间名称和令牌。

首先useState从 react 导入并设置用户名、房间名称和令牌的状态:

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
};
Enter fullscreen mode Exit fullscreen mode

接下来我们需要两个函数来处理更新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
};
Enter fullscreen mode Exit fullscreen mode

虽然这可行,但我们可以使用另一个 React hook 来优化我们的组件;useCallback

使用回调

每次调用此函数组件时,handleXXX函数都会被重新定义。它们需要成为组件的一部分,因为它们依赖于setUsernamesetRoomName函数,但它们每次都会相同。useCallback是一个 React hook,允许我们记忆函数。也就是说,如果它们在函数调用之间相同,就不会被重新定义。

useCallback接受两个参数:需要被记忆的函数以及该函数依赖项的数组。如果函数的任何依赖项发生变化,则意味着被记忆的函数已过期,然后该函数将被重新定义并再次被记忆。

在这种情况下,这两个函数之间没有任何依赖关系,因此一个空数组就足够了(钩子setState中的函数useState在函数内部被视为常量)。重写此函数时,我们需要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
};
Enter fullscreen mode Exit fullscreen mode

当用户提交表单时,我们需要将用户名和房间名称发送到服务器,以换取用于进入房间的访问令牌。我们也将在此组件中创建该函数。

我们将使用fetch API将数据以 JSON 格式发送到端点,接收并解析响应,然后使用它将setToken令牌存储在我们的状态中。我们也将用 包装这个函数useCallback,但在这种情况下,该函数将依赖于usernameroomName,因此我们将它们作为依赖项添加到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
};
Enter fullscreen mode Exit fullscreen mode

在该组件的最后一个函数中,我们将添加一个注销功能。这将把用户从房间中弹出,并返回到大厅。为此,我们将 token 设置为null。同样,我们将其封装在 中,useCallback没有任何依赖项。

  const handleLogout = useCallback(event => {
    setToken(null);
  }, []);

  return <div></div> // we'll build up our response later
};
Enter fullscreen mode Exit fullscreen mode

此组件主要负责协调其下方的组件,因此在创建这些组件之前,无需进行太多渲染。接下来,我们将创建 Lobby 组件,用于渲染请求用户名和房间名称的表单。

大厅组件

在 中创建一个新文件src/Lobby.js。此组件不需要存储任何数据,因为它会将所有事件传递给其父组件 VideoChat。渲染组件时,它会被传递username和 ,roomName以及处理每个更改和提交表单的函数。我们可以解构这些 props,以便以后更轻松地使用它们。

组件的主要工作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;
Enter fullscreen mode Exit fullscreen mode

让我们更新VideoChat组件以渲染 ,Lobby除非我们有token,否则我们将渲染usernameroomNametoken。我们需要在文件顶部导入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;
};
Enter fullscreen mode Exit fullscreen mode

为了使其显示在页面上,我们还需要将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;
Enter fullscreen mode Exit fullscreen mode

确保应用仍在运行(或使用 重启应用npm run dev),然后在浏览器中打开它,您将看到一个表单。填写用户名和房间名称并提交,视图将更改为显示您选择的名称以及从服务器检索到的令牌。

填写表单并提交,您将在页面上看到用户名、房间名称和令牌。

Room 组件

现在我们已经向应用程序添加了用户名和房间名称,我们可以使用它们加入 Twilio 视频聊天室。要使用 Twilio 视频服务,我们需要 JS SDK,请使用以下命令安装它:

npm install twilio-video --save
Enter fullscreen mode Exit fullscreen mode

src在目录中创建一个名为 的新文件Room.js。使用以下样板文件开始。我们将在此组件中使用 Twilio Video SDK 以及useStateuseEffect钩子。我们还将从父组件获取roomNametoken和作为 props handleLogoutVideoChat

import React, { useState, useEffect } from 'react';
import Video from 'twilio-video';

const Room = ({ roomName, token, handleLogout }) => {

});

export default Room;
Enter fullscreen mode Exit fullscreen mode

该组件首先会使用 token 和 roomName 连接到 Twilio 视频服务。连接后,我们会得到一个room对象,并存储它。房间还包含一个参与者列表,这些参与者会随着时间推移而变化,因此我们也会存储它们。我们将使用以下数组useState来存储这些参与者,初始值为null房间,初始值为参与者:

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

在加入房间之前,我们先为这个组件渲染一些内容。我们将映射参与者数组,以显示每个参与者的身份,并显示房间中本地参与者的身份:

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>
  );
});
Enter fullscreen mode Exit fullscreen mode

让我们更新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;
};
Enter fullscreen mode Exit fullscreen mode

在浏览器中运行此程序将显示房间名称和注销按钮,但没有参与者身份,因为我们尚未连接并加入房间。

当您现在提交表格时,您将看到房间名称,

我们已掌握加入房间所需的所有信息,因此应该在组件首次渲染时触发连接操作。我们还希望在组件销毁后立即退出房间(在后台保留 WebRTC 连接毫无意义)。这些都是副作用。

对于基于类的组件,这里可以使用componentDidMountcomponentWillUnmount生命周期方法。对于 React Hooks,我们将使用useEffect 钩子

useEffect

useEffect是一个函数,它接受一个方法,并在组件渲染完成后运行该方法。当组件加载时,我们需要连接到视频服务,同时还需要一些函数,可以在参与者加入或离开房间时分别运行,以在状态中添加和删除参与者。

让我们通过在 JSX 之前添加此代码来开始构建我们的钩子Room.js

  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);
    });
  });
Enter fullscreen mode Exit fullscreen mode

这使用tokenroomName连接到 Twilio 视频服务。连接完成后,我们设置房间状态,为其他参与者的连接或断开连接设置一个监听器,并使用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.localParticipant.tracks.forEach(function(trackPublication) {
            trackPublication.track.stop();
          });
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  });
Enter fullscreen mode Exit fullscreen mode

setRoom注意,这里我们使用了之前获取的函数的回调版本useState。如果你传递一个函数,setRoom它将使用之前的值(在本例中是现有的房间,我们称之为 )进行调用currentRoom,并将状态设置为你返回的值。

但我们还没完。在当前状态下,此组件每次重新渲染时都会退出已加入的房间并重新连接。这不太理想,所以我们需要告诉它何时应该清理并再次运行效果。就像useCallback我们通过传递效果所依赖的变量数组来实现一样。如果变量发生了变化,我们希望先清理,然后再次运行效果。如果变量没有变化,则无需再次运行效果。

观察函数,我们可以看到,如果roomNametoken发生变化,我们期望连接到不同的房间或以不同的用户身份连接。让我们将这些变量也作为数组传递给useEffect

    return () => {
      setRoom(currentRoom => {
        if (currentRoom && currentRoom.localParticipant.state === 'connected') {
          currentRoom.localParticipant.tracks.forEach(function(trackPublication) {
            trackPublication.track.stop();
          });
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  }, [roomName, token]);
Enter fullscreen mode Exit fullscreen mode

请注意,我们在此效果中定义了两个回调函数。您可能认为应该像之前一样将它们包装起来useCallback,但事实并非如此。由于它们是效果的一部分,因此它们仅在依赖项更新时运行。您也不能在回调函数中使用钩子,它们必须直接在组件或自定义钩子中使用

这个组件基本完成了。让我们检查一下它目前是否正常工作,刷新应用程序并输入用户名和房间名称。加入房间后,您应该会看到您的身份信息。点击注销按钮将返回大厅。

现在,当您提交表单时,您将看到之前的所有内容,以及您自己的身份。

最后一个步骤是呈现视频通话中的参与者,并将他们的视频和音频添加到页面。

参与者组件

在 中创建一个src名为 的新组件Participant.js。我们将从通常的样板开始,尽管在这个组件中我们将使用三个钩子,useState即 和useEffect(我们已经见过),以及useRef。我们还将participant在 props 中传递一个对象,并使用 跟踪参与者的视频和音频轨道useState

import React, { useState, useEffect, useRef } from 'react';

const Participant = ({ participant }) => {
  const [videoTracks, setVideoTracks] = useState([]);
  const [audioTracks, setAudioTracks] = useState([]);
};

export default Participant;
Enter fullscreen mode Exit fullscreen mode

当我们从参与者获取视频或音频流时,我们需要将其附加到<video><audio>元素。由于 JSX 是声明式的,我们无法直接访问 DOM(文档对象模型),因此我们需要通过其他方式获取对 HTML 元素的引用。

React 通过refsuseRef hook。要使用 ref,我们需要预先声明它们,然后在 JSX 中引用它们。useRef在渲染任何内容之前,我们使用 hook 创建 ref:

const Participant = ({ participant }) => {
  const [videoTracks, setVideoTracks] = useState([]);
  const [audioTracks, setAudioTracks] = useState([]);

  const videoRef = useRef();
  const audioRef = useRef();
 });
Enter fullscreen mode Exit fullscreen mode

现在,让我们返回我们想要的 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>
  );
 });
Enter fullscreen mode Exit fullscreen mode

我还将和标签的属性设置<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
Enter fullscreen mode Exit fullscreen mode

接下来我们使用参与者对象设置音频和视频轨道的初始值,使用我们刚刚编写的函数设置 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">
Enter fullscreen mode Exit fullscreen mode

请注意,钩子仅依赖于participant对象,除非参与者发生变化,否则不会被清理并重新运行。

我们还需要一个useEffect钩子来将视频和音频轨道附加到 DOM,这里我只展示其中一个,即视频版本,但如果您将视频替换为音频,音频效果是一样的。该钩子将从状态中获取第一个视频轨道,如果存在,则将其附加到我们之前使用 ref 捕获的 DOM 节点。您可以使用 ref 引用当前的 DOM 节点videoRef.current。如果我们附加了视频轨道,我们还需要返回一个函数,以便在清理过程中将其分离。

  }, [participant]);

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

  return (
    <div className="participant">
Enter fullscreen mode Exit fullscreen mode

重复这个钩子,我们就可以开始从组件audioTracks渲染组件了在文件顶部导入组件,然后用组件本身替换显示标识的段落。ParticipantRoomParticipant

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>
  );
});
Enter fullscreen mode Exit fullscreen mode

现在刷新应用程序,加入一个房间,你就会在屏幕上看到自己的身影。打开另一个浏览器,加入同一个房间,你会看到自己的身影出现两次。点击退出按钮,你就会回到大厅。

成功了!现在你应该可以看到自己在和自己视频聊天了。

结论

在 React 中使用 Twilio Video 进行构建需要更多工作,因为需要处理各种副作用。从发出请求获取令牌、连接到视频服务,到操作 DOM 来连接<video><audio>元素,有很多事情需要你去理解。在本文中,我们了解了如何使用useStateuseCallbackuseEffectuseRef控制这些副作用,以及如何仅使用函数式组件来构建我们的应用。

希望本文能帮助你理解 Twilio Video 和 React Hooks。该应用的所有源代码均可在 GitHub 上获取,方便你拆解和重新组合。

要进一步了解 React Hooks,请查看非常详尽的官方文档关于 hooks 思考的可视化,并查看Dan Abramov 的深入研究useEffect(这是一篇很长的文章,但值得一看,我保证)。

如果您想了解有关使用 Twilio Video 进行构建的更多信息,请查看有关在视频聊天期间切换摄像头在视频聊天中添加屏幕共享的帖子。

如果您在 React 中构建了这些或任何其他很酷的视频聊天功能,请在评论中、Twitter 上或通过电子邮件philnash@twilio.com告诉我。

文章来源:https://dev.to/twilio/build-a-video-chat-with-react-hooks-3955
PREV
31 个前端开发学习主题,助您提升代码质量
NEXT
Javascript 6 中的 console.clear JavaScript 控制台方法,类似 Taylor Swift 民谣歌词