如何解决React中useEffect结合Socket.io的无限重渲染问题
解决Socket.io聊天应用中useEffect无限重渲染的问题
你的代码里有两个关键问题导致了无限重渲染,直接修正就能解决:
1. 修复sendMessage函数的致命错误
你在sendMessage里错误调用了自身(sendMessage(...)),这会触发无限递归调用,每次调用都会更新状态,进而引发组件重渲染。正确的做法是调用setMessageList来更新消息列表:
const sendMessage = async () => { if (currentMessage !== "") { const messageData = { room: room, author: username, message: currentMessage, time: `${new Date(Date.now()).getHours()}:${new Date(Date.now()).getMinutes()}` }; await socket.emit("send_message", messageData); // 这里改成setMessageList,不是sendMessage setMessageList((list)=>[...list,messageData]); // 发送后清空输入框 setCurrentMessage(""); } };
2. 给useEffect添加清理函数,避免重复绑定监听
每次组件重渲染时,如果没有清理之前的Socket.io监听,新的监听会叠加绑定,导致收到消息时多次触发状态更新,进而引发重渲染。在useEffect的返回值中添加清理函数,移除监听:
useEffect(() => { // 定义监听回调 const handleReceiveMessage = (data) => { setMessageList((list) => [...list, data]); setCurrentMessage(""); }; socket.on("receive_message", handleReceiveMessage); // 组件卸载或依赖变化时,移除监听 return () => { socket.off("receive_message", handleReceiveMessage); }; }, [socket]);
3. 可选优化:稳定化sendMessage函数
由于sendMessage被用作按钮点击和输入框回车事件的回调,每次组件重渲染都会创建新的函数实例,虽然不是这次无限循环的主因,但可以用useCallback包裹它,减少不必要的重渲染:
import { useState, useEffect, useCallback } from 'react'; // ... const sendMessage = useCallback(async () => { if (currentMessage !== "") { const messageData = { room: room, author: username, message: currentMessage, time: `${new Date(Date.now()).getHours()}:${new Date(Date.now()).getMinutes()}` }; await socket.emit("send_message", messageData); setMessageList((list)=>[...list,messageData]); setCurrentMessage(""); } }, [currentMessage, username, room, socket]);
修正后的完整代码
import React from 'react'; import ScrollToBottom from 'react-scroll-to-bottom'; import { useState, useEffect, useCallback } from 'react'; function Chat({socket,username,room}) { const [currentMessage, setCurrentMessage] = useState(""); const [messageList, setMessageList] = useState([]); const sendMessage = useCallback(async () => { if (currentMessage !== "") { const messageData = { room: room, author: username, message: currentMessage, time: `${new Date(Date.now()).getHours()}:${new Date(Date.now()).getMinutes()}` }; await socket.emit("send_message", messageData); setMessageList((list)=>[...list,messageData]); setCurrentMessage(""); } }, [currentMessage, username, room, socket]); useEffect(() => { const handleReceiveMessage = (data) => { setMessageList((list) => [...list, data]); setCurrentMessage(""); }; socket.on("receive_message", handleReceiveMessage); return () => { socket.off("receive_message", handleReceiveMessage); }; }, [socket]); return ( <div className='chat-window'> <div className='chat-header'> <p>Live Chat</p> </div> <div className='chat-body'> <ScrollToBottom className='message-container'> {messageList.map((messageContent, index)=>{ // 给map添加key,避免React警告 return ( <div key={index} className='message' id={username===messageContent.author?"you":"other"}> <h1>{messageContent.message}</h1> </div> ); })} </ScrollToBottom> </div> <div className='chat-footer'> <input type="text" value={currentMessage} placeholder='Hey..' onChange={(event) => { setCurrentMessage(event.target.value); }} onKeyPress={(event)=>{ event.key==='Enter'&&sendMessage(); }} /> <button onClick={sendMessage}>►</button> </div> </div> ); } export default Chat;
另外,还给map添加了key属性,避免React的列表渲染警告,这是React开发的最佳实践。
内容的提问来源于stack exchange,提问作者Robert Downey Jr.
相关产品推荐
相关产品推荐

