从 0 实现一个 WebSocket 聊天室(React + Node.js 全栈教程,超详细讲解)
目标:封装一个可复用的 Hook,管理连接、接收消息、心跳机制、重连等。功能:展示消息、输入内容、发送消息、显示连接状态。
·
前言
这篇文章将带你从 0 开始,一步步实现一个能多人实时聊天的系统,包括:
- 前端:用 React 构建聊天界面,实时发送接收消息
- 后端:用 Node.js 搭建 WebSocket 服务,处理多人通信
整体架构预览
浏览器(前端)
└─ React App(WebSocket 客户端)📨
│
▼
WebSocket 连接(ws://localhost:3001)
│
▼
Node.js(服务端)
└─ ws 库(WebSocket 服务器)
技术选型
- 前端:React + TypeScript + Tailwind CSS
- 后端:Node.js + ws(轻量级 WebSocket 库)
- 网络协议:WebSocket(基于 TCP 的双向通信协议)
一、前端:React 聊天室实现(含消息收发 + 自动重连)
src/
├── App.tsx // 登录用户名
├── ChatRoom.tsx // 聊天主界面
├── useWebSocket.ts // 封装连接逻辑
└── index.css // Tailwind 样式导入
1.1 useWebSocket.ts:封装 WebSocket 逻辑
目标:封装一个可复用的 Hook,管理连接、接收消息、心跳机制、重连等。
// src/useWebSocket.ts
import { useEffect, useRef, useState } from "react";
// 封装 WebSocket 的 Hook
export function useWebSocket(url: string, username: string) {
const ws = useRef<WebSocket | null>(null); // WebSocket 实例
const heartbeat = useRef<any>(null); // 心跳定时器
const reconnectTimer = useRef<any>(null); // 自动重连定时器
const [status, setStatus] = useState<"connecting" | "open" | "closed">("connecting");
const [messages, setMessages] = useState<{ sender: string; text: string }[]>([]);
// 创建连接的函数(可重复调用)
const connect = () => {
ws.current = new WebSocket(url); // 建立连接
// 连接成功
ws.current.onopen = () => {
setStatus("open");
// 告诉服务器“我加入了聊天室”
ws.current?.send(JSON.stringify({ type: "join", sender: username }));
// 每 30 秒发一个“ping”保持连接
heartbeat.current = setInterval(() => {
ws.current?.send(JSON.stringify({ type: "ping" }));
}, 30000);
};
// 收到服务器消息时触发
ws.current.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
// 如果是聊天消息,加入到消息列表
if (data.type === "chat") {
setMessages((prev) => [...prev, { sender: data.sender, text: data.text }]);
}
} catch {}
};
// 连接断开(包括关闭和异常)
ws.current.onclose = () => {
setStatus("closed");
clearInterval(heartbeat.current);
// 3 秒后尝试重新连接
reconnectTimer.current = setTimeout(connect, 3000);
};
ws.current.onerror = () => {
setStatus("closed");
};
};
// 提供一个发送消息的函数
const sendMessage = (text: string) => {
if (ws.current?.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify({ type: "chat", sender: username, text }));
}
};
// 页面加载时连接;页面关闭时断开
useEffect(() => {
connect();
return () => {
clearInterval(heartbeat.current);
clearTimeout(reconnectTimer.current);
ws.current?.close();
};
}, [url]);
return { status, messages, sendMessage };
}
1.2 ChatRoom.tsx:聊天室界面组件
功能:展示消息、输入内容、发送消息、显示连接状态
// src/ChatRoom.tsx
import { useState } from "react";
import { useWebSocket } from "./useWebSocket";
export default function ChatRoom({ username }: { username: string }) {
const { status, messages, sendMessage } = useWebSocket("ws://localhost:3001", username);
const [input, setInput] = useState("");
return (
<div className="max-w-md mx-auto mt-8 p-4 border rounded bg-white shadow">
<h2 className="text-xl font-bold mb-4">👋 欢迎,{username}</h2>
{/* 显示当前连接状态 */}
<div className="text-sm text-gray-500 mb-2">
状态:{status === "open" ? "✅ 已连接" : "⏳ 连接中/断开"}
</div>
{/* 消息展示区域 */}
<div className="h-64 overflow-auto bg-gray-50 border p-2 mb-2 rounded">
{messages.map((msg, idx) => (
<div key={idx} className="mb-1">
<strong>{msg.sender}</strong>: {msg.text}
</div>
))}
</div>
{/* 输入框 + 发送按钮 */}
<div className="flex gap-2">
<input
className="flex-1 border px-2 py-1 rounded"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="说点什么吧"
/>
<button
className="bg-blue-600 text-white px-4 py-1 rounded"
onClick={() => {
if (input.trim()) {
sendMessage(input);
setInput(""); // 清空输入框
}
}}
>
发送
</button>
</div>
</div>
);
}
1.3 App.tsx:用户名登录页
// src/App.tsx
import { useState } from "react";
import ChatRoom from "./ChatRoom";
export default function App() {
const [username, setUsername] = useState("");
const [joined, setJoined] = useState(false);
// 未登录则显示输入框
if (!joined) {
return (
<div className="flex flex-col items-center mt-32 gap-4">
<h1 className="text-2xl font-bold">🔐 请输入用户名进入聊天室</h1>
<input
className="border rounded px-3 py-2"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button
className="bg-green-600 text-white px-4 py-2 rounded"
onClick={() => username.trim() && setJoined(true)}
>
进入聊天室
</button>
</div>
);
}
return <ChatRoom username={username} />;
}
1.4 启动前端项目
1.4.1 创建项目
npm create vite@latest websocket-chat --template react-ts
cd websocket-chat
npm install
1.4.2 安装 Tailwind
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
1.4.3 配置 tailwind.config.js
content: ["./index.html", "./src/**/*.{ts,tsx}"],
1.4.4 配置 tailwind.config.js
@tailwind base;
@tailwind components;
@tailwind utilities;
1.4.5 配置 tailwind.config.js
npm run dev
二、后端:Node.js + ws 实现 WebSocket 服务端
2.1 初始化项目
mkdir websocket-server
cd websocket-server
npm init -y
npm install ws
2.3 server.js:服务端实现代码(详细注释)
// server.js
const WebSocket = require("ws");
// 创建一个 WebSocket 服务器,监听 3001 端口
const wss = new WebSocket.Server({ port: 3001 });
console.log("✅ WebSocket 服务已启动:ws://localhost:3001");
const clients = new Set(); // 存储所有连接的客户端
// 新客户端连接时
wss.on("connection", (ws) => {
console.log("🔌 有客户端连接");
clients.add(ws);
ws.isAlive = true;
// 客户端发来消息
ws.on("message", (msg) => {
try {
const data = JSON.parse(msg);
if (data.type === "join") {
// 广播“某某加入了聊天室”
broadcast({ type: "chat", sender: "系统", text: `${data.sender} 加入了聊天室` });
}
if (data.type === "chat") {
// 聊天内容广播给所有人
broadcast({ type: "chat", sender: data.sender, text: data.text });
}
if (data.type === "ping") {
ws.isAlive = true; // 用于心跳
}
} catch (err) {
console.error("❌ 无效消息", msg.toString());
}
});
// 客户端断开连接
ws.on("close", () => {
clients.delete(ws);
console.log("📴 有客户端断开连接");
});
});
// 心跳检测(每 30 秒检查是否仍连接)
setInterval(() => {
for (let ws of clients) {
if (!ws.isAlive) {
console.log("⚠️ 心跳失败,断开连接");
ws.terminate(); // 强制断开连接
clients.delete(ws);
continue;
}
ws.isAlive = false;
ws.ping(); // 向客户端发送 ping
}
}, 30000);
// 广播消息给所有客户端
function broadcast(data) {
const msg = JSON.stringify(data);
for (let client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(msg);
}
}
}
2.4 启动后端服务
node server.js
会看到
✅ WebSocket 服务已启动:ws://localhost:3001
2.5 最终效果展示
- 多人打开前端页面输入不同用户名
- 任意一个人发言,所有人即时收到
- 自动断线重连
- 服务端日志打印连接/断开/心跳等信息
更多推荐


所有评论(0)