如何使用 React 和 Socket.io 构建实时群聊应用程序

2025-05-25

如何使用 React 和 Socket.io 构建实时群聊应用程序

本文讲解了“Socket.io”框架的含义,并演示了如何使用 React 创建一个简单的群聊应用。GitHub仓库链接如下。如有任何问题,欢迎随时评论,我将随时为您解答。

目标

本教程的目的是解释 Socket.io V4 的工作原理,并简化它与 React 等前端框架的使用

目录

  • 先决条件
  • 入门
  • 设置服务器
  • 设置 React
  • 将客户端连接到服务器
    • 创建服务器连接
    • 重构 React App
    • 创建路线
    • 将 React 连接到服务器
  • 处理 CORS
  • 连接到不同的房间
  • 消息传递
    • 欢迎辞
    • 发送消息
  • 断开
  • 结论

先决条件

什么是 Socket.io?

Socket.io 是一个 JavaScript 库,允许浏览器和服务器之间进行双向安全实时通信。这意味着,如果用户发送数据,则该数据的接收者将立即收到,具体取决于网速。

工作原理

根据Socket.io的说明,客户端会尝试建立WebSocket连接(如果可能),如果无法建立,则会转而使用 HTTP 长轮询。WebSocket 负责在客户端和服务器之间建立连接。Socket.io 利用 WebSocket 提供的连接来传输数据。

让我们深入了解本文的过程。

入门

在您想要的文件夹中打开终端,然后创建一个新文件夹并移入其中:



mkdir react-chat-app
cd react-chat-app
npx create-react-app .


Enter fullscreen mode Exit fullscreen mode

导航回项目根文件夹,初始化项目并安装服务器依赖项:



npm init -y
npm i express socket.io concurrently nodemon


Enter fullscreen mode Exit fullscreen mode

并发功能允许我们同时运行多个命令,而无需创建另一个终端。这确实有助于我们在一个终端中同时运行 React 和服务器端。

Nodemon是一个当文件目录发生更改时自动重启服务器的工具。

设置服务器

所有安装完成后,我们server.js在项目根目录中创建一个文件并要求所有必要的依赖项:



const http = require("http");
const express = require("express");


Enter fullscreen mode Exit fullscreen mode

为 socket.io 设置服务器与通常的 Express 设置不同。根据socket.io文档,我们使用 node server 来创建 socket.io 服务http



const app = express()
const server = http.createServer(app)
const io = socketio(server)

const PORT = process.env.PORT || 5000

server.listen(PORT, () => console.log(`Server is Quannected to Port ${PORT}`))


Enter fullscreen mode Exit fullscreen mode

该常量PORT利用 ES 模块检查我们的应用是否已部署。如果应用未部署,则返回 5000。

script我们需要在文件内的标签中添加几行代码package.json,以便我们可以使用以下命令运行服务器npm



    "start": "node server.js",
    "server": "nodemon server",
    "dev": "concurrently \"npm run server\" \"cd client && npm start\""


Enter fullscreen mode Exit fullscreen mode

让我们在终端中试用我们的应用程序:



npm run dev


Enter fullscreen mode Exit fullscreen mode

设置 React

进入react-chat-app并打开我们的终端来安装我们将在本文中使用的依赖项:



npm i react-router socket.io-client query-string react-router-dom


Enter fullscreen mode Exit fullscreen mode

Socket.io-client是 socket.io 创建的依赖项,用于帮助连接到服务器中的 socket.io。

查询字符串帮助我们从url地址栏中获取参数。

将客户端连接到服务器

这就是我们的消息应用程序的启动点。在这里,我们将在 React 应用程序和服务器应用程序之间创建一个 socket.io 连接。

创建服务器连接

客户端必须发起监听事件server.js才能连接到服务器:



io.on("connection", (socket) => {
    console.log('A Connection has been made')
    socket.on('disconnect', ()=> {
        console.log('A disconnection has been made')
    })
})


Enter fullscreen mode Exit fullscreen mode

常量io正在监听来自connection客户端的连接,当连接建立后,它会为该连接创建一个特殊的套接字。套接字作为箭头函数的参数传递,保存着刚刚建立的连接的属性。在我们的代码中,连接socket(即 )监听连接何时断开。由于连接断开,套接字会被移除。

重构 React App

在我们连接到服务器之前,我们需要对新的 React 应用程序进行一些重构。

首先,我们需要删除 React 应用中预先创建的一些文件。删除文件夹中的所有内容,然后在同一文件夹中src创建。将以下代码添加到index.jssrcindex.js



import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));


Enter fullscreen mode Exit fullscreen mode

App.js为了防止 React 对我们大喊大叫,我们需要在与 相同的目录中创建。我们需要在 App.js 中index.js添加一个函数组件,该组件将返回一条简单的欢迎消息:



import React from "react";

const App = () => {
    <h1>App Successfully rendered.</h1>
}
export default App;


Enter fullscreen mode Exit fullscreen mode

创建路线

让我们创建一个名为 的文件夹componentssrc它将包含我们 React 应用中的所有不同组件。在该components文件夹中,创建一个Home.js和一个Chat.js文件。创建完成后,返回到 来app.js设置我们的路由:



import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from "./components/Home";
import Chat from "./components/Chat";

const App = () => (
  <Router>
    <Route path="/" exact component={Home}/>
    <Route path="/chat" component={Chat} />
  </Router>
);

export default App;


Enter fullscreen mode Exit fullscreen mode

为了清楚起见,HomeChat文件位于中,components并且components位于中src在单词末尾components有一个,记下来,这样你就不会遇到错误💗。s

Home我们创建了一条路线,在访问主页和Chat访问聊天页面时使用功能组件。

Home组件将包含一个表单,该表单会将我们重定向到Chat指定组的组件。打开Home.js文件并设置我们的表单:



import React, { useState } from "react";
import { Link } from "react-router-dom";

const Home = () => {
  const [name, setName] = useState("");
  const [room, setRoom] = useState("");

  return (
      <div>
        <h1>Home Page</h1>
        <div>
          <input
            placeholder="Name"
            type="text"
            onChange={(event) => setName(event.target.value)}
            required
          />
        </div>
        <div>
          <input
            placeholder="Room"
            type="text"
            onChange={(event) => setRoom(event.target.value)}
            required
          />
        </div>
        <Link
          onClick={(e) => (!name || !room ? e.preventDefault() : null)}
          to={`/chat?name=${name}&room=${room}`}
        >
          <button type="submit">
            Sign In
          </button>
        </Link>
      </div>
  );
};

export default Home;



Enter fullscreen mode Exit fullscreen mode

为了尽可能缩短本文,我们将不包含任何样式。您可以根据需要添加自定义样式。

我们导入了useStatestate 值来保存用户输入的姓名和房间信息。 了解更多关于useState 的信息。

在所有输入标签中,我们都有一个onChange事件用于监听输入值的变化并将其保存在 中state。我们利用Link从 导入的 ,当且仅当我们的和状态变量有值时,react-router-dom将我们重定向到聊天页面(将nameroom作为参数传递)nameroom

将 React 连接到服务器

我们已经设置了表单,下一步是在我们的服务器中创建连接和断开连接chat.js



import React, { useState, useEffect } from "react";
import queryString from "query-string";
import io from "socket.io-client";

let socket;

const Chat = ({ location }) => {
  const [name, setName] = useState("");
  const [room, setRoom] = useState("");
  const ENDPOINT = "http://localhost:5000";

  useEffect(() => {
    const { name, room } = queryString.parse(location.search);
    socket = io(ENDPOINT);
    setRoom(room);
    setName(name);
  }, [location.search]);

  return <div>Chat</div>;
};

export default Chat;



Enter fullscreen mode Exit fullscreen mode

App.js文件使用 传递了一个 prop 给Chat.jslocationreact-router-dom并且这个locationprop 包含url。然后我们从 中获取参数(名称和房间),并url使用query-string依赖项将它们设置为状态变量。useEffect每次运行时 的location.search值都会发生变化。阅读更多内容,请参阅useEffect

处理 CORS

在该代码块中useEffect,我们创建了一个实例socket并传入了服务器的 Endpoint 。由于我们尝试在两个不同的路由之间传输数据,http://localhost:5000这将违反跨域资源共享策略。CORS
CORS 错误![图片说明](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9cog93uj3zr50vz7dlhi.png)<br>

Socket.ioV3 开始,我们需要CORS在服务器中明确启用以确保客户端成功连接到服务器。

别慌🙂,我们需要optionsserver.jsSocket.io 中创建连接,以允许来自客户端的连接。由于我们已经声明了常量io,我们只需向连接添加以下选项:



const io = require("socket.io")(server, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
    allowedHeaders: ["my-custom-header"],
    credentials: true,
  },
});


Enter fullscreen mode Exit fullscreen mode

连接到不同的房间

我们必须在服务器中创建一个接收器,等待接收来自客户端的新连接。user.js在与服务器文件相同的目录中创建一个新文件,该文件将负责管理我们的用户:



let users = [];

exports.addUser = ({ id, name, room }) => {
  if (!name || !room) return { error: "name and room required." };
  const user = { id, name, room };

  users.push(user);

  return { user };
};


Enter fullscreen mode Exit fullscreen mode

users变量将包含所有已连接的用户。如果名称或房间为空,则返回错误;否则,我们将用户添加到数组 users 中并返回该用户。

我们必须为客户端创建一个监听事件,以便他们加入我们的不同房间server.js



const {addUser} = require('./user')
io.on("connection", (socket) => {

  socket.on("join", ({ name, room }, callBack) => {
    const { user, error } = addUser({ id: socket.id, name, room });

    if (error) return callBack(error);

    socket.join(user.room);
    callBack(null);
  });
//The rest of the code


Enter fullscreen mode Exit fullscreen mode

socket.on监听来自客户端的任何连接,名称为 ,"join"然后期望name,并room作为客户端的参数。回调函数会发送错误信息(如果出现错误),否则返回 null,*必须从服务器返回 *。

我们需要从客户端连接到事件join并将输入的名称和房间作为参数发送到服务器。



useEffect(() => {
 // The rest of the code 
  socket.emit("join", { name, room }, (error) => {
    if (error) alert(error);
  });
}, [location.search]);


Enter fullscreen mode Exit fullscreen mode

消息传递

好吧,我们到了😮。

欢迎辞

当用户加入房间时,我们必须向用户发出欢迎消息。

我们所有的消息都来自服务器。当用户发送消息时,我们必须先将该消息发送到服务器,然后再将其发送回客户端。服务器发出消息,客户端接收消息。

导航至chat.js创建连接:



const [messages, setMessages] = useState([]);
useEffect(() => {
  socket.on("message", (message) => {
    setMessages((messages) => [...messages, message]);
  });
}, []);


Enter fullscreen mode Exit fullscreen mode

我们创建了另一个 useEffect,它接收来自服务器的所有消息并将其设置为messages状态变量。
我们需要在返回块中为用户渲染这些消息。我们需要使用 JSX 将所有消息渲染给用户:



return (
  <div>
    {messages.map((val, i) => {
      return (
        <div key={i}>
          {val.text}
          <br />
          {val.user}
        </div>
      );
    })}
  </div>
);


Enter fullscreen mode Exit fullscreen mode

我们通过messages状态变量进行了映射,将键指定为索引以避免react错误,并返回了textuser服务器传递下来的。
让我们从我们的连接到客户端创建的连接server.js



io.on("connection", (socket) => {
  socket.on("join", ({ name, room }, callBack) => {

    //The rest of the code

    socket.emit("message", {
      user: "Admin",
      text: `Welocome to ${user.room}`,
    });

    // The rest of the code


Enter fullscreen mode Exit fullscreen mode

我们正在发射message连接,并将usertext作为参数传递。

我们还必须通知组中的其他用户,有新用户加入了。导航至server.js



socket.broadcast
  .to(user.room)
  .emit("message", { user: "Admin", text: `${user.name} has joined!` });


Enter fullscreen mode Exit fullscreen mode

客户端始终在监听emitmessage就像message是连接的名称或标识。
我们刚刚编写的代码正在向房间内的其他用户广播,告诉他们有新用户刚刚加入了群组。

发送消息

发送消息的过程如下:我们将获取用户输入的消息,将其发送到服务器,然后服务器将该消息发送给群组中的每个人。让我们打开chat.js并创建input字段:



const handleSubmit = (e) => {
  e.preventDefault();
  if (message) {
    socket.emit("sendMessage", { message });
    setMessage("");
  } else alert("empty input");
};

return (
  <div>

    // The rest of the code

    <form action="" onSubmit={handleSubmit}>
      <input
        type="text"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <input type="submit" />
    </form>
  </div>
  );


Enter fullscreen mode Exit fullscreen mode

我们正在向一个新socket事件发送消息。它接收来自用户的消息,并将其发送到新创建的套接字事件sendMessage。使用事件将消息发送到服务器后sendMessage,打开你的事件服务器server.js,并为该事件创建连接sendMessage



socket.on("join", ({ name, room }, callBack) => {

  //The rest of the code

  socket.on("sendMessage", ({ message }) => {
    io.to(user.room).emit("message", {
      user: user.name,
      text: message,
    });
  });
});


Enter fullscreen mode Exit fullscreen mode

我们从客户端收到消息后,将收到的消息发送给群组中的每个人。

断开

这是本文的最后一部分。当用户结束聊天并希望断开连接时,我们需要向群里的每个人发送一条消息,告知他们有用户刚刚断开连接。让我们打开user.js文件并创建一个函数,负责从数组中删除用户:



exports.removeUser = (id) => {
  const index = users.findIndex((user) => user.id === id);
  return users[index];
};



Enter fullscreen mode Exit fullscreen mode

该函数removeUser会请求一个id,找到具有该 ID 的用户并返回该用户。
我们必须导入removeUser我们的server.js,并向返回的组中的每个人发送断开连接消息user



const { addUser, removeUser } = require("./user");
io.on("connection", (socket) => {
    // The rest of the code

    socket.on("disconnect", () => {
    const user = removeUser(socket.id);
    console.log(user);
    io.to(user.room).emit("message", {
      user: "Admin",
      text: `${user.name} just left the room`,
    });
    console.log("A disconnection has been made");
  });
 });


Enter fullscreen mode Exit fullscreen mode

重启服务器后,刷新聊天页面可能会出现错误。最好的办法是从主页重新登录。

结论

恭喜,我们已成功创建具有和的实时聊天应用React程序Socket.io

这是GitHub 仓库的链接。差点忘了感谢我的一位YouTube 博主,他对这篇文章提供了很大的帮助。

感谢你一直陪我到最后💗。如果你想把这个聊天应用部署到 Heroku,我有一篇文章讲解了如何将 React 和 Node 应用部署到 Heroku
至此,我依然是 Fredrick Emmanuel (divofred)😁😁❤❤

文章来源:https://dev.to/divofred/how-to-build-a-realtime-group-chat-application-with-react-and-socketio-2jf0
PREV
为初学者制作一个简单的 CSS 时间线!
NEXT
测验📣:您对异步 JavaScript 的理解程度如何?