在实时聊天、多人协作等场景中,房间内用户列表的实时展示是核心需求之一,结合React的Context API和Socket.IO可以高效实现这个功能,既保证状态管理的简洁性,又能满足实时通信的要求。

核心实现思路
整体实现分为服务端和客户端两部分:服务端通过Socket.IO管理房间和用户状态,监听用户加入、离开房间的事件并广播更新后的用户列表;客户端通过Context API定义用户列表的全局状态,连接Socket.IO监听服务端推送的用户列表更新事件,同步更新Context中的状态,再让需要的组件消费Context数据完成展示。
服务端实现
基础环境搭建
首先初始化Node.js项目,安装依赖:
// 初始化项目后执行安装命令 // npm install express socket.io
房间与用户管理逻辑
服务端需要维护房间和用户的关系,这里用一个对象存储每个房间对应的用户列表:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
// 存储房间用户列表 结构: { roomId: [userId1, userId2] }
const roomUsersMap = {};
// 处理用户加入房间
const handleJoinRoom = (socket, roomId, userId) => {
// 如果用户已经在其他房间 先离开原房间
Object.keys(roomUsersMap).forEach(room => {
if (roomUsersMap[room].includes(userId)) {
roomUsersMap[room] = roomUsersMap[room].filter(id => id !== userId);
socket.leave(room);
// 广播原房间用户列表更新
io.to(room).emit('room_users_update', roomUsersMap[room]);
}
});
// 加入新房间
socket.join(roomId);
if (!roomUsersMap[roomId]) {
roomUsersMap[roomId] = [];
}
if (!roomUsersMap[roomId].includes(userId)) {
roomUsersMap[roomId].push(userId);
}
// 向新房间广播更新后的用户列表
io.to(roomId).emit('room_users_update', roomUsersMap[roomId]);
};
// 处理用户离开房间
const handleLeaveRoom = (socket, roomId, userId) => {
if (roomUsersMap[roomId]) {
roomUsersMap[roomId] = roomUsersMap[roomId].filter(id => id !== userId);
socket.leave(roomId);
// 广播房间用户列表更新
io.to(roomId).emit('room_users_update', roomUsersMap[roomId]);
}
};
io.on('connection', (socket) => {
console.log('新用户连接:', socket.id);
// 监听加入房间事件
socket.on('join_room', ({ roomId, userId }) => {
handleJoinRoom(socket, roomId, userId);
});
// 监听离开房间事件
socket.on('leave_room', ({ roomId, userId }) => {
handleLeaveRoom(socket, roomId, userId);
});
// 监听断开连接事件
socket.on('disconnect', () => {
// 遍历所有房间 移除断开连接的用户
Object.keys(roomUsersMap).forEach(roomId => {
const userIndex = roomUsersMap[roomId].findIndex(id => id === socket.id);
if (userIndex !== -1) {
roomUsersMap[roomId].splice(userIndex, 1);
io.to(roomId).emit('room_users_update', roomUsersMap[roomId]);
}
});
console.log('用户断开连接:', socket.id);
});
});
server.listen(3001, () => {
console.log('服务端运行在端口3001');
});
客户端实现
创建用户列表Context
首先定义Context,包含用户列表状态和更新方法:
import React, { createContext, useContext, useState, useEffect } from 'react';
import { io } from 'socket.io-client';
// 创建Context
const RoomUsersContext = createContext();
// 创建Provider组件
export const RoomUsersProvider = ({ children }) => {
const [roomUsers, setRoomUsers] = useState([]);
const [socket, setSocket] = useState(null);
// 初始化Socket连接
useEffect(() => {
const newSocket = io('http://127.0.0.1:3001');
setSocket(newSocket);
return () => {
newSocket.close();
};
}, []);
// 加入房间的方法
const joinRoom = (roomId, userId) => {
if (socket) {
socket.emit('join_room', { roomId, userId });
// 监听该房间的用户列表更新
socket.on('room_users_update', (users) => {
setRoomUsers(users);
});
}
};
// 离开房间的方法
const leaveRoom = (roomId, userId) => {
if (socket) {
socket.emit('leave_room', { roomId, userId });
}
};
return (
<RoomUsersContext.Provider value={{ roomUsers, joinRoom, leaveRoom, socket }}>
{children}
</RoomUsersContext.Provider>
);
};
// 自定义Hook方便消费Context
export const useRoomUsers = () => {
const context = useContext(RoomUsersContext);
if (!context) {
throw new Error('useRoomUsers必须在RoomUsersProvider内使用');
}
return context;
};
组件中使用Context展示用户列表
在入口文件包裹Provider,然后在需要的组件中消费用户列表数据:
// 入口文件 App.js
import React from 'react';
import { RoomUsersProvider } from './RoomUsersContext';
import RoomUserList from './RoomUserList';
function App() {
return (
<RoomUsersProvider>
<div className="app">
<RoomUserList />
</div>
</RoomUsersProvider>
);
}
export default App;
用户列表展示组件:
import React, { useEffect, useState } from 'react';
import { useRoomUsers } from './RoomUsersContext';
const RoomUserList = () => {
const { roomUsers, joinRoom, leaveRoom } = useRoomUsers();
const [roomId, setRoomId] = useState('');
const [userId, setUserId] = useState('');
// 模拟用户加入房间
const handleJoin = () => {
if (roomId && userId) {
joinRoom(roomId, userId);
}
};
// 模拟用户离开房间
const handleLeave = () => {
if (roomId && userId) {
leaveRoom(roomId, userId);
}
};
return (
<div className="room-user-list">
<div className="operation">
<input
type="text"
placeholder="输入房间ID"
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
/>
<input
type="text"
placeholder="输入用户ID"
value={userId}
onChange={(e) => setUserId(e.target.value)}
/>
<button onClick={handleJoin}>加入房间</button>
<button onClick={handleLeave}>离开房间</button>
</div>
<h3>当前房间用户列表</h3>
<ul>
{roomUsers.map((user, index) => (
<li key={index}>{user}</li>
))}
</ul>
</div>
);
};
export default RoomUserList;
注意事项
- 服务端需要做好跨域配置,避免客户端连接时出现跨域问题
- 用户断开连接时要及时清理对应的用户数据,避免用户列表残留无效数据
- Socket事件监听要注意避免重复绑定,防止多次触发更新逻辑
- 如果项目中有多个房间相关的状态,可以扩展Context的内容,统一管理所有房间相关的全局状态
Context_APISocket.IO用户列表房间管理修改时间:2026-06-24 16:21:42