簡體   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