You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js+React+Socket.IO聊天室如何展示房间内已加入用户

房间在线用户列表实现方案

核心逻辑:服务端维护每个房间的在线用户映射关系,在用户加入房间、断开连接时更新列表,实时广播给房间内所有成员;客户端监听用户列表更新事件,拿到数据后渲染即可。

一、服务端修改步骤

  1. 首先在全局定义存储结构,记录每个房间对应的在线用户
    在io.on("connection")代码块外新增:
// 存储各房间在线用户,key为房间ID,value为用户数组:[{socketId, username}]
const roomUsers = new Map();
  1. 修改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);
});
  1. 修改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做自定义渲染。

  1. 新增用户列表状态,修改加入房间时的传参
    原来加入房间只传了房间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;
  1. 后续可根据自己的UI设计调整用户列表的样式,如果需要支持用户主动退出房间,只需新增leave_room事件,复用disconnect时移除用户、广播列表的逻辑即可。

内容的提问来源于stack exchange,提问作者Atef Cohen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:03:39