Socket.io 和 React:从服务器接收数据并在组件中设置状态的正确方法?

Socket.io and React: Proper way too receive data from server and set state in a component?

尝试运行一个基本的聊天应用程序,但在发送消息时遇到过度重新呈现的问题。这是客户端的适用代码:

const [chatMessages, setChatMessages] = useState([]);
const sendChat = (e) => {
    socket.emit("sendMessage", e.target.elements.message.value);
}

useEffect(() => {
  socket.on("receiveMessage", (chatMessage) => {
    setChatMessages([...chatMessages, chatMessage]);
      console.log(chatMessages);
  });
}, [chatMessages]);
return (
    {chatMessages.map((message) => <p>{message}</p>)}
)

然后,在服务器上:

io.on("connection", (socket) => {
    socket.on("sendMessage", (chatMessage) => {
        console.log("message sent");
        io.to(roomId).emit("receiveMessage", chatMessage);
    });
}

当我这样做时,消息被成功发送和接收,但它导致它发生很多次(控制台):

[]
[]
[{...}]
[{...}]
(2) [{...}, {...}]

在第三条消息中,这是记录的内容。到第六条或第七条消息时,整个页面都停止了,因为它记录了大约 100 次。

我试过以下方法:

  1. useEffect() 中有一个空的依赖项数组。这确实修复了重新渲染,但引入了一个新问题。最新的消息是唯一一条被保存并取代了最后一条消息,所以你一次只能看到一条消息。

  2. 一起从useEffect()中取出来。这只会使问题恶化,并导致每条消息的重新呈现次数更多。

如有任何帮助,我们将不胜感激。谢谢!

由于您对 chatMessages 有依赖性,每次 chatMessages 更改时,它都会创建一个新的侦听器。这就是为什么当收到更多消息时它变得越来越慢的原因。

你可以做两件事:

  1. 您在 useEffect 方法中本地维护 chatMessages。你可以展开那个数组,然后用展开的数组调用 setChatMessages 。执行此操作时,您可以删除 chatMessagesuseEffect 的依赖性并且仍然拥有所有消息。作为一种好的做法,您应该 return 一个函数,该函数将在组件卸载时删除事件侦听器。

const [chatMessages, setChatMessages] = useState([]);
const sendChat = (e) => {
    socket.emit("sendMessage", e.target.elements.message.value);
}

useEffect(() => {
  let localMessages = [];
  const callback = (chatMessage) => {
    localMessages = [...localMessages, chatMessage];
    setChatMessages(localMessages);
      console.log(localMessages);
  };
  socket.on("receiveMessage", callback); 
  return () => {
    socket.off("receiveMessage", callback); 
  }
}, []);
return (
    {chatMessages.map((message) => <p>{message}</p>)}
)

  1. 您或许可以使用 useRef 来存储值。但是,当值更改时,这不会触发 re-render 的 UI,这可能不是您想要的。所以这可能不是一个好的选择。

问题

您正在创建套接字事件处理程序,当 chatMessages 状态更新时 但没有清除它们 。如果您编辑您的代码或组件重新呈现等......然后添加另一个套接字事件处理程序。多个处理程序将开始堆叠并将多个意外状态更新排队。

此外,由于 React 状态更新是异步处理的,因此您无法在更新入队后立即记录状态并期望看到更新后的状态。为此使用单独的 useEffect 挂钩。

解决方案

  1. 添加 useEffect 清除函数以删除事件处理程序和 re-enclose 处理程序回调中更新的 chatMessages 状态数组。

     useEffect(() => {
       const handler = (chatMessage) => {
         setChatMessages([...chatMessages, chatMessage]);
       }
    
       socket.on("receiveMessage", handler);
    
       return () => socket.off("receiveMessage", handler);
     }, [chatMessages]);
    
  2. 添加一个 useEffect 清理函数,删除依赖项,以便效果在组件安装时运行一次,并使用功能状态更新从以前的状态而不是初始状态正确更新在回调附件中。

     useEffect(() => {
       const handler = (chatMessage) => {
         setChatMessages(chatMessages => [...chatMessages, chatMessage]);
       }
    
       socket.on("receiveMessage", handler);
    
       return () => socket.off("receiveMessage", handler);
     }, []);
    

两者之间,第二个选项是更优的解决方案,但您选择哪个是您的决定。

要记录 chatMessages 状态更新:

useEffect(() => {
  console.log(chatMessages);
}, [chatMessages]);