React中如何避免Socket.IO发送重复数据?
客户端发送消息后重复接收,服务器端出现MaxListenersExceededWarning警告
当客户端发送message时,服务器端和消息展示容器div都会收到大量重复消息,同时终端出现内存泄漏警告,以下是相关代码及问题详情:
全局Socket Provider Context
为所有子组件提供Socket实例的上下文:
// fileName : SocketProvider.js import { createContext } from "react"; import { io } from "socket.io-client"; const socket = io("http://localhost:8080/", { withCredentials: true }); const SocketContext = createContext(); export const SocketProvider = ({ children }) => { return ( <SocketContext.Provider value={{ socket }}> {children} </SocketContext.Provider> ); }; export default SocketContext;
App.js中的包裹方式
<SocketProvider> <Main/> </SocketProvider>
主页面(Main.jsx)
包含消息展示区、输入框及发送逻辑:
// filename : Main.jsx // 所有依赖已正确导入 import ... const Main = ()=>{ const { socket } = useContext(SocketContext); const [message, setMessage] = useState(""); const [conversation, setConversation] = useState([]); // 其他全局上下文 const { currentUserId } = useContext(UserContext); const { selectedUserId, setSelectedUserId } = useContext(SelectedUserContext); // 切换selectedUserId的代码 // ... ... .... // // 初始化时发送userJoined事件 useEffect(() => { socket.emit("userJoined", { senderUserId: currentUserId, receiverUserId: selectedUserId, }); }, [socket, currentUserId, selectedUserId]); // 监听消息的useEffect useEffect(() => { const onMessageReceived = (msg) => { setConversation((conversations) => [...conversations, msg]); }; socket.on("message", onMessageReceived); return () => { socket.off("message", onMessageReceived); }; }, [socket]); // 发送消息函数 const sendMessage = useCallback(() => { if (!message) return; socket.emit("message", { msg: message, sentBy: "Unknown" }); setMessage(""); }, [socket, message]); // 监听回车键发送 useEffect(() => { const handleEnterKeyPressed = (e) => { if (e.keyCode === 13 && e.shiftKey === false && e.ctrlKey === false) sendMessage(); }; element.addEventListener("keydown", handleEnterKeyPressed); return () => { element.removeEventListener("keydown", handleEnterKeyPressed); }; }, [sendMessage]); return ( <div className="container"> <div className="msgContainer"> {conversation.map((data)=>( <h1 className="...">{data.msg}</h1> <p className="...">{data.sentBy}</p> ))} </div> <input value={message} onChange={(e)=>setMessage(e.target.value)} placeholder="Enter message here"/> <img className="..." src={sendIcon} onClick={sendMessage}/> </div> ); } export default Main;
服务端监听代码(server.js)
//filename: server.js io.on("connection", (socket) => { socket.on("userJoined", (userJoinedData) => { const chatRoomId = generateChatRoomId(userJoinedData); socket.on("message", async (msgData) => { // 此处会多次打印,次数超过11次 console.log(`DEBUG: New Message Arrived :`, msgData); // 保存消息到MongoDB const savedMessage = await saveMsg(msgData); io.to(chatRoomId).emit("message", savedMessage); }); }); });
终端警告信息
(node:14040) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added to [Socket]. Use emitter.setMaxListeners() to increase limit
问题原因及解决方案
核心问题1:服务端重复绑定message事件
每次触发userJoined事件时,都会给当前socket新增一个message事件监听器,导致同一个socket上绑定了多个相同的监听器,收到消息时会多次执行处理逻辑,同时触发内存泄漏警告。
修复方案:
将message监听器移到userJoined外部,通过变量存储当前聊天房间ID,避免重复绑定:
//filename: server.js io.on("connection", (socket) => { let currentChatRoomId = null; // 只绑定一次message监听器 socket.on("message", async (msgData) => { if (!currentChatRoomId) return; console.log(`DEBUG: New Message Arrived :`, msgData); const savedMessage = await saveMsg(msgData); io.to(currentChatRoomId).emit("message", savedMessage); }); socket.on("userJoined", (userJoinedData) => { currentChatRoomId = generateChatRoomId(userJoinedData); // 加入目标聊天房间 socket.join(currentChatRoomId); }); // 断开连接时清理房间ID socket.on("disconnect", () => { currentChatRoomId = null; }); });
核心问题2:客户端优化(潜在体验提升)
虽然客户端的监听清理逻辑无问题,但发送消息时可以先更新本地会话,减少等待服务端响应的延迟:
const sendMessage = useCallback(() => { if (!message) return; const newMsg = { msg: message, sentBy: "Unknown" }; // 先更新本地会话,提升体验 setConversation(prev => [...prev, newMsg]); socket.emit("message", newMsg); setMessage(""); }, [socket, message]);
内容的提问来源于stack exchange,提问作者Messi
相关产品推荐
相关产品推荐

