如何使用 React 和 Socket.io 构建实时群聊应用程序
本文讲解了“Socket.io”框架的含义,并演示了如何使用 React 创建一个简单的群聊应用。GitHub仓库链接如下。如有任何问题,欢迎随时评论,我将随时为您解答。
目标
本教程的目的是解释 Socket.io V4 的工作原理,并简化它与 React 等前端框架的使用
目录
- 先决条件
- 入门
- 设置服务器
- 设置 React
- 将客户端连接到服务器
- 创建服务器连接
- 重构 React App
- 创建路线
- 将 React 连接到服务器
- 处理 CORS
- 连接到不同的房间
- 消息传递
- 欢迎辞
- 发送消息
- 断开
- 结论
先决条件
- ES6 语法
- 关于React和Node 的知识
- 文本编辑器,最好是Visual Studio Code或Atom
- NodeJS已安装
什么是 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 .
导航回项目根文件夹,初始化项目并安装服务器依赖项:
npm init -y
npm i express socket.io concurrently nodemon
并发功能允许我们同时运行多个命令,而无需创建另一个终端。这确实有助于我们在一个终端中同时运行 React 和服务器端。
Nodemon是一个当文件目录发生更改时自动重启服务器的工具。
设置服务器
所有安装完成后,我们server.js
在项目根目录中创建一个文件并要求所有必要的依赖项:
const http = require("http");
const express = require("express");
为 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}`))
该常量PORT
利用 ES 模块检查我们的应用是否已部署。如果应用未部署,则返回 5000。
script
我们需要在文件内的标签中添加几行代码package.json
,以便我们可以使用以下命令运行服务器npm
:
"start": "node server.js",
"server": "nodemon server",
"dev": "concurrently \"npm run server\" \"cd client && npm start\""
让我们在终端中试用我们的应用程序:
npm run dev
设置 React
进入react-chat-app
并打开我们的终端来安装我们将在本文中使用的依赖项:
npm i react-router socket.io-client query-string react-router-dom
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')
})
})
常量io
正在监听来自connection
客户端的连接,当连接建立后,它会为该连接创建一个特殊的套接字。套接字作为箭头函数的参数传递,保存着刚刚建立的连接的属性。在我们的代码中,连接socket
(即 )监听连接何时断开。由于连接断开,套接字会被移除。
重构 React App
在我们连接到服务器之前,我们需要对新的 React 应用程序进行一些重构。
首先,我们需要删除 React 应用中预先创建的一些文件。删除文件夹中的所有内容,然后在同一文件夹中src
创建。将以下代码添加到:index.js
src
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
为了防止 React 对我们大喊大叫,我们需要在与 相同的目录中创建。我们需要在 App.js 中index.js
添加一个函数组件,该组件将返回一条简单的欢迎消息:
import React from "react";
const App = () => {
<h1>App Successfully rendered.</h1>
}
export default App;
创建路线
让我们创建一个名为 的文件夹components
,src
它将包含我们 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;
为了清楚起见,
Home
和Chat
文件位于中,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;
为了尽可能缩短本文,我们将不包含任何样式。您可以根据需要添加自定义样式。
我们导入了useState
state 值来保存用户输入的姓名和房间信息。 了解更多关于useState 的信息。
在所有输入标签中,我们都有一个onChange
事件用于监听输入值的变化并将其保存在 中state
。我们利用Link
从 导入的 ,当且仅当我们的和状态变量有值时,react-router-dom
将我们重定向到聊天页面(将name
和room
作为参数传递)。name
room
将 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;
该App.js
文件使用 传递了一个 prop 给Chat.js
,location
,react-router-dom
并且这个location
prop 包含url
。然后我们从 中获取参数(名称和房间),并url
使用query-string
依赖项将它们设置为状态变量。useEffect
每次运行时 的location.search
值都会发生变化。阅读更多内容,请参阅useEffect
。
处理 CORS
在该代码块中useEffect
,我们创建了一个实例socket
并传入了服务器的 Endpoint 。由于我们尝试在两个不同的路由之间传输数据,http://localhost:5000
这将违反跨域资源共享策略。CORS
从
Socket.io
V3 开始,我们需要CORS
在服务器中明确启用以确保客户端成功连接到服务器。
别慌🙂,我们需要options
在server.js
Socket.io 中创建连接,以允许来自客户端的连接。由于我们已经声明了常量io
,我们只需向连接添加以下选项:
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
allowedHeaders: ["my-custom-header"],
credentials: true,
},
});
连接到不同的房间
我们必须在服务器中创建一个接收器,等待接收来自客户端的新连接。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 };
};
该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
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]);
消息传递
好吧,我们到了😮。
欢迎辞
当用户加入房间时,我们必须向用户发出欢迎消息。
我们所有的消息都来自服务器。当用户发送消息时,我们必须先将该消息发送到服务器,然后再将其发送回客户端。服务器发出消息,客户端接收消息。
导航至chat.js
创建连接:
const [messages, setMessages] = useState([]);
useEffect(() => {
socket.on("message", (message) => {
setMessages((messages) => [...messages, message]);
});
}, []);
我们创建了另一个 useEffect,它接收来自服务器的所有消息并将其设置为messages
状态变量。
我们需要在返回块中为用户渲染这些消息。我们需要使用 JSX 将所有消息渲染给用户:
return (
<div>
{messages.map((val, i) => {
return (
<div key={i}>
{val.text}
<br />
{val.user}
</div>
);
})}
</div>
);
我们通过messages
状态变量进行了映射,将键指定为索引以避免react
错误,并返回了text
从user
服务器传递下来的。
让我们从我们的连接到客户端创建的连接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
我们正在发射message
连接,并将user
和text
作为参数传递。
我们还必须通知组中的其他用户,有新用户加入了。导航至server.js
:
socket.broadcast
.to(user.room)
.emit("message", { user: "Admin", text: `${user.name} has joined!` });
客户端始终在监听emit
。message
就像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>
);
我们正在向一个新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,
});
});
});
我们从客户端收到消息后,将收到的消息发送给群组中的每个人。
断开
这是本文的最后一部分。当用户结束聊天并希望断开连接时,我们需要向群里的每个人发送一条消息,告知他们有用户刚刚断开连接。让我们打开user.js
文件并创建一个函数,负责从数组中删除用户:
exports.removeUser = (id) => {
const index = users.findIndex((user) => user.id === id);
return users[index];
};
该函数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");
});
});
重启服务器后,刷新聊天页面可能会出现错误。最好的办法是从主页重新登录。
结论
恭喜,我们已成功创建具有和的实时聊天应用React
程序Socket.io
。
这是GitHub 仓库的链接。差点忘了感谢我的一位YouTube 博主,他对这篇文章提供了很大的帮助。
感谢你一直陪我到最后💗。如果你想把这个聊天应用部署到 Heroku,我有一篇文章讲解了如何将 React 和 Node 应用部署到 Heroku。
至此,我依然是 Fredrick Emmanuel (divofred)😁😁❤❤