React中socket.io消息重复收发、broadcast.emit不生效问题
React 集成 socket.io 消息重复收发、广播不生效问题修复
问题现象
React项目中使用socket.io时,消息会重复发送/接收2到4次,初步判断和组件重复渲染相关。已尝试将socket初始化init函数从container.jsx移动到app.jsx、清理浏览器缓存,两种方案均未解决问题。控制台日志显示ChatList组件存在重复渲染情况,同时后端使用socket.broadcast.emit推送消息时存在不生效问题。

问题相关代码
客户端socket封装
import { useEffect } from "react"; import io from "socket.io-client"; let socket; export const init = () => { console.log("Connecting..."); socket = io.connect("http://localhost:3000", { transports: ["websocket"], }); socket.on("connect", () => console.log("Connected!")); }; export const sendMessage = (message) => { if (socket) { socket.emit("new-message", message); } }; export const onNewMessage = (callback) => { if (!socket) return; socket.on("receive-message", (message) => { callback(message); console.log("Received message: ", message); }); };
服务端代码
const app = require("express")(); const http = require("http").Server(app); const io = require("socket.io")(http); const cors = require("cors"); const Messages = require("./lib/Messages"); app.use(cors()); app.get("/", (req, res) => { res.end("Merhaba Socket.IO"); }); io.on("connection", (socket) => { console.log("a user connected"); Messages.list((data) => { console.log(data); socket.emit("message-list", data); }); socket.on("new-message", (message) => { console.log(message); Messages.upsert({ message }); socket.broadcast.emit("receive-message", message); }); socket.on("disconnect", () => console.log("a user disconnected")); }); http.listen(process.env.PORT || "3000", () => { console.log("listening on *:3000"); });
ChatList.jsx 组件代码
import { useChat } from "../context/ChatContext"; import styles from "./style.module.css"; import ChatItem from "./ChatItem"; export default function ChatList() { const { messages } = useChat(); console.log(messages); return ( <div className={styles.chatlist}> <div> {messages.map((item, key) => ( console.log("Message: ",item), <ChatItem key={key} value={item} /> ))} </div> </div> ); }
根因分析
- 消息重复收发核心原因:两点问题共同导致
- socket初始化没有做单例校验,React重渲染、useEffect重复执行时会创建多个socket连接,同一个客户端和后端建立了多条通信链路
- 事件监听没有做解绑逻辑,每次调用
onNewMessage都会给socket实例新增一个receive-message事件回调,消息到达时所有绑定过的回调会依次执行,直接导致同一条消息被处理多次
socket.broadcast.emit不生效原因:对API逻辑理解有误,该方法只会向除当前发送消息的socket之外的所有连接推送消息,发送消息的用户本人不会收到这条消息,业务要求全员可见时就会出现“发送方收不到消息”的异常表现。
修复方案
- 补全socket单例校验,避免重复创建连接
修改init方法,初始化前先判断socket实例是否已存在且处于连接状态,已初始化则直接返回:
export const init = () => { // 新增单例判断,已连接则不重复创建 if (socket?.connected) return; console.log("Connecting..."); socket = io.connect("http://localhost:3000", { transports: ["websocket"], }); socket.on("connect", () => console.log("Connected!")); };
- 增加事件监听清理逻辑,避免重复绑定
修改onNewMessage方法,绑定新监听前先移除旧的同事件监听,同时新增断开连接的清理方法,供组件卸载时调用:
export const onNewMessage = (callback) => { if (!socket) return; // 绑定新回调前先移除旧的同事件监听 socket.off("receive-message"); socket.on("receive-message", (message) => { callback(message); console.log("Received message: ", message); }); }; // 新增清理方法 export const disconnectSocket = () => { if (socket) { socket.off("receive-message"); socket.disconnect(); socket = null; } }
- 修正后端广播逻辑
如果需要发送方也能收到自己发的消息,将socket.broadcast.emit替换为io.emit(向所有连接全量广播),或者在广播后单独给当前socket推送一次:
socket.on("new-message", (message) => { console.log(message); Messages.upsert({ message }); // 全量广播,所有连接(包括发送方)都能收到消息 io.emit("receive-message", message); });
- 规范React侧生命周期调用
将socket初始化、监听绑定的逻辑放在useEffect中,依赖项设为空数组保证仅在组件挂载时执行一次,return的清理函数中调用断开连接的方法,避免组件重渲染时重复执行逻辑:
// 业务组件中使用示例 useEffect(() => { init(); onNewMessage((msg) => { // 写入消息列表更新逻辑 }); return () => { disconnectSocket(); }; }, []);
内容的提问来源于stack exchange,提问作者Selcukusu
相关产品推荐
相关产品推荐

