在删除另一个组件中的所有侦听器之后,为什么socket.io无法在React组件上进一步工作?

问题描述

经过数周的调试并不得不执行一个可怕的解决方法才能使我的React应用程序正常工作,我才发现了问题,作为React的初学者,这让我感到困惑,所以我发布此问题以听取您的建议。

我有一个React应用程序,或者说一个Ionic react应用程序(但它与普通的React Web应用程序实际上是相同的),在这里我使用着名的socket.io库与后端通信并实时接收消息时间。

为简单起见,这是我的代码的构建方式:

import React,{ useEffect,useState } from 'react';
import socketIOClient from 'socket.io-client';
// bunch of other imports ....

const serverHost = config.localUrl;
const socket = socketIOClient(serverHost);

const App: React.FC = () => {

 
  const [state1,setState1] = useState([]);
  const [state2,setState2] = useState([]);

  useEffect(() => {
    socket.on('connect_error',() => {
      console.log("connection error .. please make sure the server is running");
      // socket.close();
    });

    return () => {
      console.log("deconnecting the socket... ");
      socket.close();
    }
  },[])

  useEffect( () => {
    socket.emit('init',"initialize me");
    socket.on('onInit',(configs: any) => {
         setState1(configs);
    });
  },[])


  const reset = () => {
    socket.removeAllListeners(); // the focus is on this line here.
    state1.forEach( (s: any) => {
      s.checked = false;
      s.realTimeValue = "";
    })
    setState1([]);
  }

  
  return (
    <IonApp>
      <IonToolbar color="primary">
        <IonTitle >Test</IonTitle>
      </IonToolbar>
      <IonContent>
      
      
        <Component1
          socket={socket}
          reset={reset}
        />

        <IonList>
          {state1.map((s: any,idx: number) =>
            <Component2 key={s.name}
              s={s}
              socket={socket}
              
            />)
          }

          
        </IonList>
      </IonContent>

      <CustomComponent socket={socket} />

    </IonApp>
  );
};

export default App;

如您所见,我的应用程序很简单。我传递了套接字对象以侦听子组件中的事件,这种情况一直很好,直到有一天我注意到如果用户在UI中删除了Component2之一,那么我将收到一个警告,指出socket.io已收到这是一个事件,但是组件已卸载,这将导致内存泄漏。这是反应中的一个著名警告,这是警告:

Can't perform a React state update on an unmounted component. This is a no-op,but it indicates a memory leak in your application. To fix,cancel all subscriptions and clean up listeners.

在谷歌搜索之后,我发现socket.io有一个内置函数可以执行此操作,这就是我在reset函数调用socket.removeAllListeners()在这里变得很有趣,这很好用,现在用户可以安全删除了。但是,CustomComponent(应用程序中的最后一个组件)中的socket.on调用不再起作用。如果我在重置功能中注释了socket.removeAllListeners()行,那么CustomComponent中的socket.on调用将再次开始侦听和接收消息。

令人惊讶的是,这不仅不适用于我应用程序中的最后一个组件CustomComponent。但是,它对于其他组件也能正常工作!如您在代码中所看到的,我将reset函数作为对Component1支持,因此它与CustomComponent无关。

有人知道为什么它不起作用以及如何解决

注意

我实现的解决方法是将CustomComponent中的socket.on函数移到useEffect内,以便在ComponentDidMount和ComponentDidUpdate发生时始终会触发该函数。这里的问题是socket.on会触发多次。因此,如果我收到来自服务器的消息,那么我会在浏览器中看到该函数连续被调用了5次。

Thisthis问题也在这里与我的问题有关。

解决方法

socket.removeAllListeners()将从套接字中删除所有侦听器,包括那些仍由已安装和侦听的组件添加的侦听器。 组件在安装时应调用socket.on,在卸载时应调用socket.off。这可以通过使用useEffect来实现:

const [configs,setConfigs] useState([]);

useEffect(() => {
    const onInit = configs => setConfigs(configs);

    socket.on('onInit',onInit);

    socket.emit('init',"initialize me");

    // return a function that unsubscribes the handler from the socket
    // you have to pass the handler which you passed to socket.on before to only remove that handler
    return () => socket.off('onInit',onInit);
},[]);

经验法则是,订阅某些东西作为安装它的副作用的组件在卸载时也必须取消订阅。它永远不能只做两者之一,而只能退订它所订阅的内容。当组件仅订阅特定事件时,调用socket.removeAllListeners()很容易出错。它将中断其他任何组件的订阅。

如果组件没有打开套接字,则不应关闭套接字,并且不应订阅也不取消订阅的信号。不能将副作用归于一处会使您头疼。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...