Situation:
情况:
I'm setting up an express server with socket.io and a client using ReactJS. This is my server:
我正在使用Socket.IO设置一个Express服务器,并使用ReactJS设置一个客户端。这是我的服务器:
//... another requirement
const socket = require("./socket");
const app = express();
const server = http.createServer(app);
const port = process.env.PORT || 3000;
//... some parsers ...
routes(app);
socket(server);
server.listen(port);
console.log("Server started on: " + port);
And this is my socket:
这是我的插座:
const socketIO = require("socket.io");
const socket = server => {
const io = socketIO(server);
const chat = io.of("/chat");
chat.on("connection", client => {
console.log("User started chat!");
client.on("disconnect", () => {
console.log("User left chat!");
});
client.on("sendMessage", msg => {
const { id, text } = msg;
console.log(id + " says: " + text);
client.broadcast.emit("incomingMessage", {
//... response data
});
});
});
};
In the client, the Chat Page component will handle sending message to socket and receiving message from socket:
在客户端,聊天页面组件将处理向Socket发送消息和从Socket接收消息:
//_ChatBox in Chat Page
const _ChatBox = (/*props*/) => {
const chat = io("http://localhost" + ":3000/chat");
chat.on("incomingMessage", message => {
console.log("Receive message");
//...handle income message
});
const sendMessageToSocket = (data) => {
chat.emit("sendMessage", data);
};
return ( /*JSX*/);
};
Problem:
问题:
Everytime a user accesses Chat Page, socket will receive connect event and log User started chat!
to console once, but actually, it has logged twice, that means the socket has handled the connect event twice.
每次用户访问聊天页面时,Socket都会收到连接事件并记录用户开始聊天!来控制一次,但实际上,它已经记录了两次,这意味着套接字已经处理了两次连接事件。
And when _ChatBox receives message, it will log Receive message
and display that message, but what it has done is log that twice and display the message twice, that also means the socket has emitted the incomeMessage event twice.
当_Chatbox收到消息时,它将记录接收到的消息并显示该消息,但它所做的是记录该消息两次并显示该消息两次,这也意味着套接字已经发出了两次incomeMessage事件。
What i want: I want the socket only handles those events once.
我想要的:我希望套接字只处理这些事件一次。
更多回答
It looks like your component is rendering twice, so calling const chat = io("http://localhost" + ":3000/chat");
twice, giving two connection messages.
您的组件看起来呈现了两次,因此调用const chat=io(“http://localhost”+“:3000/Chat”);两次,给出两条连接消息。
You can use a useEffect()
hook to only connect on the first render. Passing in an empty array as the second argument means the effect will only be called once:
可以使用useEffect()挂钩仅在第一次渲染时连接。将空数组作为第二个参数传入意味着该效果将只调用一次:
const _ChatBox = (/*props*/) => {
useEffect(()=>{
const chat = io("http://localhost" + ":3000/chat");
chat.on("incomingMessage", message => {
console.log("Receive message");
//...handle income message
});
const sendMessageToSocket = (data) => {
chat.emit("sendMessage", data);
};
}, []);
return ( /*JSX*/);
};
Create socket inside index.js file and pass as a prop to the root app component like this :-
在index.js文件中创建套接字,并将其作为道具传递给根应用程序组件,如下所示:-
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import socketIOClient from "socket.io-client"
var socket = socketIOClient('http://localhost:9000');
ReactDOM.render(
<React.StrictMode>
<App socket={socket} />
</React.StrictMode>,
document.getElementById('root')
);
Now Use This socket to emit events like this:-
现在使用此套接字发出如下事件:-
import React from "react"
import './App.css';
class App extends React.Component {
componentDidMount() {
this.props.socket.emit('test')
}
render() {
return (
<div>
hello
</div>
);
}
}
export default App;
I had a similar problem. I solved it by writing inside my functional component
a useEffect that opens socket onMount (and onUpdate) but CLOSES SOCKET ON UNMOUNT.
我也遇到过类似的问题。我解决这个问题的方法是在我的功能组件中编写一个useEffect,它在mount(和onUpdate)上打开套接字,但在卸载时关闭套接字。
useEffect(()=>{
const socket = io("http://localhost:3000")
socket.on(userId, (arg) => {
//stuff
});
return () => socket.emit('end'); //Close socket on UNMOUNT
})
I had the same problem, and I solved it.
我也遇到了同样的问题,我也解决了。
return() => {
socket.off("whatever_you_named").off();
}
As you see here, you have to use the useEffect
function that React is providing. Next, you have to attach the below code inside of your useEffect
function to prevent receiving the same message twice. If you have 4 listening methods, you would have to attach that codes 4 times to prevent that problem for each method.
正如您在这里看到的,您必须使用Reaction提供的useEffect函数。接下来,您必须在useEffect函数中附加以下代码,以防止两次收到相同的消息。如果您有4个侦听方法,则必须将这些代码附加4次,以防止每个方法都出现该问题。
There are a few solutions here already that contain parts of a complete answer and while this answer may already be available in pieces I think it is helpful to have it all in one place.
这里已经有一些解决方案包含了完整答案的部分内容,虽然这个答案可能已经分成几个部分提供,但我认为将所有这些都放在一个地方会很有帮助。
As others have said, you need to turn your listener on inside of a useEffect
hook but also to include a return
statement in which your listener is turned off. This way if/when the component reloads it doesn't add an additional instance of the listener.
正如其他人所说,您需要在useEffect挂钩中打开侦听器,但还需要包括一个关闭侦听器的返回语句。这样,如果/当组件重新加载时,它不会添加侦听器的额外实例。
useEffect(() => {
socket.on('event-name', eventHandlingFunction);
return () => {
socket.off('event-name', eventHandlingFunction);
};
}, [eventHandlingFunction]);
Incidentally you can also use the above structure for socket.onAny
and socket.offAny
, though you would, of course, not include the name of a single event socket was listening for.
顺便说一句,您还可以对socket.onAny和socket.offAny使用上面的结构,当然,您不会包括套接字监听的单个事件的名称。
更多回答
Thank you! But I don't understand why the component is rendered twice!
谢谢!但我不明白为什么组件会呈现两次!
React renders the component twice due to StrictMode. This mode checks if there are any unwanted side-effects in all components by basically shaking it hard. If the useEffect causes different behaviour the second mount, there's something wrong which would cause problems in a production application. Luckily there is no double render (no strictmode) in a production app. This is purely React helping you prevent potential problems when your useEffects() become more complex or even problematic.
由于StrictMode,React会将组件呈现两次。该模式通过基本上用力摇动来检查所有组件中是否有任何不想要的副作用。如果useEffect在第二次挂载时导致不同的行为,则说明存在问题,这可能会在生产应用程序中造成问题。幸运的是,在生产应用程序中没有双重渲染(没有严格模式)。这纯粹是在您的useEffect()变得更加复杂甚至有问题时帮助您防止潜在的问题。
我是一名优秀的程序员,十分优秀!