为什么 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
函数移动到 useEffect 中,以便在 ComponentDidMount 和 ComponentDidUpdate 发生时始终触发它。这里的问题是 socket.on 会触发不止一次。因此,如果我从服务器收到一条消息,那么我会在浏览器中看到该函数被连续调用 5 次。
This and this问题也和我这里的问题有关
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);
}, []);
根据经验,订阅某物作为安装它的 side-effect 的组件在卸载时也必须取消订阅。它永远不应该只做两者之一,它应该只取消订阅它自己订阅的内容。当组件仅订阅特定事件时调用 socket.removeAllListeners()
非常容易出错。它将破坏任何其他组件订阅。
如果一个组件没有打开一个套接字,它不应该关闭它,它不应该订阅一个也没有取消订阅的信号。不把属于一起的副作用放在一个地方会让你很头疼。
经过数周的调试并不得不实施一个糟糕的解决方法来让我的 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
函数移动到 useEffect 中,以便在 ComponentDidMount 和 ComponentDidUpdate 发生时始终触发它。这里的问题是 socket.on 会触发不止一次。因此,如果我从服务器收到一条消息,那么我会在浏览器中看到该函数被连续调用 5 次。
This and this问题也和我这里的问题有关
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);
}, []);
根据经验,订阅某物作为安装它的 side-effect 的组件在卸载时也必须取消订阅。它永远不应该只做两者之一,它应该只取消订阅它自己订阅的内容。当组件仅订阅特定事件时调用 socket.removeAllListeners()
非常容易出错。它将破坏任何其他组件订阅。
如果一个组件没有打开一个套接字,它不应该关闭它,它不应该订阅一个也没有取消订阅的信号。不把属于一起的副作用放在一个地方会让你很头疼。