You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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}>&#9658;</button>
            </div>
        </div>
    );
}

export default Chat;

另外,还给map添加了key属性,避免React的列表渲染警告,这是React开发的最佳实践。

内容的提问来源于stack exchange,提问作者Robert Downey Jr.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 15:10:30