You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 18中useEffect执行两次致Socket.IO连接失败的解决办法及原因?

React 18中useEffect执行两次导致Socket.IO连接失败的解决方案

问题背景

升级到React 18后,组件挂载时的空依赖useEffect会执行两次,导致Socket.IO连接失败,收到报错:

WebSocket connection to 'ws://localhost:4444/socket.io/?EIO=4&transport=websocket&sid=tvz2_YPur_k2czFbAAAo' failed: WebSocket is closed before the connection is established.

原useEffect代码如下:

useEffect(() => {
  dispatch(connect());
  return () => dispatch(disconnect());
}, []);

测试发现:移除StrictMode后代码恢复正常;移除清理函数return () => dispatch(disconnect())也能正常运行,想了解原因和可行的解决方案。

问题原因

React 18在开发环境的StrictMode中新增了「挂载-卸载-重新挂载」的检测逻辑,目的是提前暴露组件卸载时未清理资源的问题。对于空依赖的useEffect,开发环境下会按以下流程执行:

  1. 首次执行effect,发起Socket连接
  2. 立即执行清理函数,关闭还未建立完成的连接
  3. 再次执行effect,重新发起连接

这就导致第一次的连接还没建立就被强制关闭,进而抛出"连接建立前已关闭"的错误。

而移除清理函数后,第一次发起的连接不会被中途终止,即使第二次effect重复调用connect,Socket.IO通常会自动复用已有连接或处理重复请求,所以不会报错,但这属于规避问题而非解决问题,生产环境虽然StrictMode会关闭,但开发环境的异常仍需处理。

可行解决方案

方案1:用Ref标记首次挂载

通过Ref记录组件是否完成首次挂载,避免重复执行连接逻辑:

import { useEffect, useRef } from 'react';

// 组件内部
const isFirstMount = useRef(true);

useEffect(() => {
  if (isFirstMount.current) {
    isFirstMount.current = false;
    dispatch(connect());
    return () => dispatch(disconnect());
  }
}, []);

方案2:让Socket.IO连接支持重复调用

修改connect方法的逻辑,确保多次调用时不会创建重复连接,而是复用已有实例:

// 示例:Socket.IO连接封装逻辑
let socketInstance = null;

export const connect = () => {
  // 仅在实例不存在时创建新连接
  if (!socketInstance) {
    socketInstance = io('ws://localhost:4444');
    // 其他连接初始化逻辑
  }
  return socketInstance;
};

export const disconnect = () => {
  if (socketInstance) {
    socketInstance.disconnect();
    socketInstance = null;
  }
};

方案3:临时移除StrictMode(不推荐)

如果只是想临时绕开开发环境的检测,可以在项目入口文件中移除<StrictMode>的包裹,但这会失去React 18 StrictMode提供的其他资源泄漏检测能力,不建议长期使用。

内容的提问来源于stack exchange,提问作者Angel Hadzhiev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:05:27