React Router严格模式导致SocketIO事件触发两次
React Strict Mode导致Socket事件重复触发的原因及解决方案
原因分析
React的StrictMode(严格模式)在开发环境下会特意重复执行部分操作,用来帮你排查代码里的潜在问题,包括:
- 组件的构造、渲染逻辑
useEffect的回调函数useEffect的清理函数
你的代码里,StrictMode会让TablesView组件的useEffect先执行一次(绑定room_made等事件),紧接着触发清理函数,然后再重新执行一次useEffect(再次绑定room_made事件)。但你的清理函数里没移除room_made的事件监听,导致同一个事件被绑定了两次回调。服务器发一次room_made事件,客户端就会触发两次回调,最终让games数组重复添加元素。
注意:这个重复执行的行为只在开发环境存在,生产环境下StrictMode不会做这个操作,但你的代码本身存在未正确清理副作用的问题,必须修复。
解决方案
在useEffect的清理函数中,补上对room_made事件的移除监听即可:
function TablesView() { const [games, setGames] = useState([]); // ...其他状态 useEffect(() => { socket.on("room_made", function (msg) { var gameID = msg["gameID"]; var chairs = msg["chairs"]; setGames((oldGames) => [...oldGames, { gameID, chairs }]); }); socket.on("disconnect", () => { setIsConnected(false); }); return () => { socket.off("room_made"); // 新增:移除room_made事件监听 socket.off("connect"); socket.off("disconnect"); socket.off("pong"); }; }, []); // ...组件渲染逻辑 }
这样,当StrictMode第一次执行清理函数时,会移除room_made的监听,第二次执行useEffect时重新绑定,就不会出现重复监听的问题了,同时也避免了组件卸载时的内存泄漏风险。
额外优化建议
如果你的socket实例是全局共享的,建议使用命名函数作为事件回调,而不是匿名函数,这样socket.off可以精准移除指定的回调,不会影响其他组件对同一个事件的监听:
function TablesView() { const [games, setGames] = useState([]); // ...其他状态 // 定义命名回调函数 const handleRoomMade = (msg) => { var gameID = msg["gameID"]; var chairs = msg["chairs"]; setGames((oldGames) => [...oldGames, { gameID, chairs }]); }; useEffect(() => { socket.on("room_made", handleRoomMade); socket.on("disconnect", () => { setIsConnected(false); }); return () => { socket.off("room_made", handleRoomMade); // 精准移除指定回调 socket.off("connect"); socket.off("disconnect"); socket.off("pong"); }; }, []); // ...组件渲染逻辑 }
内容的提问来源于stack exchange,提问作者Sam Gates
相关产品推荐
相关产品推荐

