Socket.io连接时DOM正常更新,多开同用户标签页或断开时更新失败,原因是什么?
现有代码存在的核心问题
服务端问题
- 登录逻辑时序错误:socket连接时优先读取session中的user_id,但
login事件是连接建立后才由客户端触发,新登录用户首次连接时无法正确写入users字典 - 多标签页状态判断逻辑冗余:未在用户断开连接时清理
users字典中对应失效的socket.id,每次都全量遍历所有在线socket判断有效性,长期运行会产生大量无效数据堆积 - 事件发送逻辑错误:断开连接时触发的
disconnectList事件传递的是当前在线用户列表,而非离线用户ID,逻辑完全倒置 - 重复事件推送:用户连接时循环给所有客户端逐个推送单个在线用户信息,会产生大量冗余事件,用户量上升后性能损耗严重
客户端问题
- 无DOM删除逻辑:收到
disconnectList事件时仍调用新增用户的outputActiveUsers方法,自然无法实现离线用户移除 - 数据与DOM无关联:仅用
record数组存储已添加的用户ID,未关联对应DOM节点,需要删除时无法定位到对应li元素 - 去重逻辑不完整:仅新增时做了ID去重,没有兜底的同步逻辑,服务端推送异常时容易出现列表与实际在线状态不一致
更优实现方案
推荐采用服务端推送全量在线列表 + 客户端全量重渲染的方案,逻辑最简单不易出错,完全可以覆盖100人以下的在线场景,不需要处理复杂的增量同步逻辑。
服务端优化代码
let users = {}; // 结构:{user_id: [socket_id1, socket_id2...]} io.on("connection", async (socket) => { // 登录成功后绑定用户和socket socket.on("login", async (user_id) => { socket.handshake.session.user_id = user_id; socket.handshake.session.save(); // 写入users字典 if (!users[user_id]) users[user_id] = []; users[user_id].push(socket.id); // 推送最新在线列表 await broadcastOnlineUsers(); }); // 断开连接处理 socket.on("disconnect", async () => { const userId = socket.handshake.session.user_id; if (!userId) return; // 清理当前失效的socket.id if (users[userId]) { users[userId] = users[userId].filter(id => id !== socket.id); // 该用户所有连接都断开了,删除对应key if (users[userId].length === 0) { delete users[userId]; // 可选:推送用户离线通知 const userInfo = await utils.getUserInfo(userId); io.emit("msg", utils.formatMessage("Bot", `${userInfo.name} 已离开聊天室`)); } } // 推送最新在线列表 await broadcastOnlineUsers(); }); // 封装:获取全量在线用户信息并广播 async function broadcastOnlineUsers() { const onlineUserIds = Object.keys(users); const onlineUsers = await Promise.all(onlineUserIds.map(id => utils.getUserInfo(id))); io.emit("updateOnlineUsers", onlineUsers); } });
客户端优化代码
let ul = document.querySelector(".the-ul"); let socket = io(); // 收到全量在线用户列表直接重渲染 socket.on("updateOnlineUsers", (onlineUsers) => { // 清空现有列表 ul.innerHTML = ''; // 循环渲染所有在线用户 onlineUsers.forEach(user => { outputActiveUsers(user); }); }); // 其他原有逻辑(消息收发、提交等)保持不变 function outputActiveUsers(msg){ let li = document.createElement("li"); li.classList.add("active-people-li"); // 给li加用户ID属性,后续要做增量操作时可以直接定位 li.dataset.userId = msg.id; li.innerHTML= `<div class="active-sidebar"> <div class="div-of-img"> <img src="../files/profile.png" alt="" class="active-profile-img"> </div> <div> <a href=""><h4>${msg.name}</h4></a> <h5>Hi. Is this you, bro?</h5> </div> </div>`; ul.appendChild(li); }
DOM操作可选优化(适合在线用户量较大的场景)
如果在线用户超过100人,全量重渲染会有轻微性能损耗,可以改为增量操作:
- 用户上线时:检查是否存在
[data-user-id="${user.id}"]的节点,不存在则新增 - 用户下线时:直接找到对应
data-user-id的节点调用remove()删除即可
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

