Node.js+React+Socket.IO聊天室如何展示房间内已加入用户
房间在线用户列表实现方案
核心逻辑:服务端维护每个房间的在线用户映射关系,在用户加入房间、断开连接时更新列表,实时广播给房间内所有成员;客户端监听用户列表更新事件,拿到数据后渲染即可。
一、服务端修改步骤
- 首先在全局定义存储结构,记录每个房间对应的在线用户
在io.on("connection")代码块外新增:
// 存储各房间在线用户,key为房间ID,value为用户数组:[{socketId, username}] const roomUsers = new Map();
- 修改
join_room事件监听逻辑,接收用户名参数,更新用户列表并广播
原来的join_room只接收房间ID,现在需要同时接收用户名,加入房间后更新列表,向全房间推送最新用户列表:
socket.on("join_room", (data) => { const { room, username } = data; socket.join(room); console.log(`User with ID: ${socket.id} joined room: ${room}, username: ${username}`); // 更新房间用户列表 if (!roomUsers.has(room)) { roomUsers.set(room, []); } const userList = roomUsers.get(room); // 避免同一socket重复加入被重复记录 const existIndex = userList.findIndex(item => item.socketId === socket.id); if (existIndex < 0) { userList.push({ socketId: socket.id, username: username }); } // 向房间内所有人广播最新用户列表 io.to(room).emit("room_users", userList); });
- 修改
disconnect事件逻辑,用户断开时从所有所在房间移除,同步更新列表
socket.on("disconnect", () => { console.log("User Disconnected", socket.id); // 遍历所有房间,移除当前断开的用户 roomUsers.forEach((userList, roomId) => { const newUserList = userList.filter(item => item.socketId !== socket.id); roomUsers.set(roomId, newUserList); // 广播更新后的列表 io.to(roomId).emit("room_users", newUserList); // 房间没人了就清空key释放内存 if (newUserList.length === 0) { roomUsers.delete(roomId); } }); });
二、客户端修改步骤
主要修改ChatRoom.js文件,Chat组件可按需接收用户列表props做自定义渲染。
- 新增用户列表状态,修改加入房间时的传参
原来加入房间只传了房间ID,现在需要把用户名一起传给服务端,同时新增状态存储在线用户列表:
import io from "socket.io-client"; import { useState, useEffect } from "react"; // 引入useEffect处理事件监听 import Chat from "./Chat"; const socket = io.connect("http://localhost:3000"); function ChatRoom() { const [username, setUsername] = useState(""); const [room, setRoom] = useState(""); const [showChat, setShowChat] = useState(false); const [roomUsers, setRoomUsers] = useState([]); // 存储当前房间在线用户 // 监听服务端推送的用户列表更新 useEffect(() => { socket.on("room_users", (userList) => { setRoomUsers(userList); }); // 组件卸载时移除监听,避免内存泄漏 return () => { socket.off("room_users"); } }, []); const joinRoom = () => { if (username !== "" && room !== "") { // 传入房间ID和当前用户名 socket.emit("join_room", { room: room, username: username }); setShowChat(true); } }; return ( <div className="App"> {!showChat ? ( <div className="joinChatContainer"> <h3>Join A Chat</h3> <input type="text" placeholder="John..." onChange={(event) => { setUsername(event.target.value); }} /> <input type="text" placeholder="Room ID..." onChange={(event) => { setRoom(event.target.value); }} /> <button onClick={joinRoom}>Join A Room</button> </div> ) : ( <div className="chatContainer" style={{display: "flex"}}> {/* 在线用户列表侧边栏 */} <div className="userList" style={{width: "200px", borderRight: "1px solid #eee", padding: "12px"}}> <h4>在线用户 ({roomUsers.length})</h4> <ul style={{listStyle: "none", padding: 0}}> {roomUsers.map(user => ( <li key={user.socketId} style={{padding: "6px 0"}}> {user.username} {user.socketId === socket.id && " (我)"} </li> ))} </ul> </div> {/* 原有聊天组件 */} <Chat socket={socket} username={username} room={room} /> </div> )} </div> ); } export default ChatRoom;
- 后续可根据自己的UI设计调整用户列表的样式,如果需要支持用户主动退出房间,只需新增
leave_room事件,复用disconnect时移除用户、广播列表的逻辑即可。
内容的提问来源于stack exchange,提问作者Atef Cohen
相关产品推荐
相关产品推荐

