繁体   English   中英

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

[英]Why socket.io does not work further on react components after I remove all listeners in another component?

经过数周的调试并不得不实施一个糟糕的解决方法来让我的 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 有一个内置函数来执行此操作,即我在重置函数中调用的socket.removeAllListeners() 在这里它变得有趣,这很好用,现在用户可以安全地删除。 但是,CustomComponent(应用程序中的最后一个组件)中的socket.on调用不再起作用。 如果我在重置函数中注释socket.removeAllListeners()行,则 CustomComponent 中的socket.on调用将再次开始侦听和接收消息。

令人惊讶的是,这不仅适用于我的应用程序中的最后一个组件,即CustomComponent 但是,它适用于其他组件! 正如您在代码中看到的,我将reset函数作为道具传递给Component1 ,因此它与CustomComponent无关。

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

笔记

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

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

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()非常容易出错。 它会破坏任何其他组件订阅。

如果一个组件没有打开它,它就不应该关闭它,并且它不应该订阅一个也没有取消订阅的信号。 不把属于一起的副作用放在一个地方会让你很头疼。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM