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

React中Socket事件触发后chatMessages状态数组被重置的问题

问题背景

服务端是基于Node.js开发的Socket.IO应用,代码如下:

io.use((socket, next) => {
    socket.name = socket.handshake.headers.name;
    socket.room = socket.handshake.headers.room;
    socket.user_id = socket.handshake.headers.user_id;
    return next();
});

let all_sockets = [];

io.on("connection", (socket) => {
    const ob = {
        id: socket.id,
        room: socket.room,
        name: socket.name,
        user_id: socket.user_id
    };
    all_sockets = all_sockets.filter(el => el.user_id !== socket.user_id);
    all_sockets.push(ob);
    socket.on("message", (data) => {
        const recipients = all_sockets.filter(el => el.room === socket.room && el.id !== socket.id);
        recipients.forEach(el => {
            io.to(el.id).emit('private', data);
        })
    });
    socket.on("disconnect", (reason) => {
        all_sockets = all_sockets.filter(el => el.id !== socket.id);
    });
});

客户端是React的Chat组件,代码如下:

const Chat = () => {
    const { username } = useParams();
    const socket_url = `http://localhost:4000/`;
    const [socket, setSocket] = useState(null);
    const [chatMessages, setChatMessages] = useState([]);

    const sendMessage = () => {
        const msg = messageRef.current.value;
        const message_object = {from: username, data: msg};
        setChatMessages([...chatMessages, message_object]);
        socket.emit('message', msg);
        messageRef.current.value = '';
    }


    const connect = async () => {
        try {
            const body = {username: username};
            const response = await fetch(`http://localhost:4000/login`, body);
            const data = await response.json();
            const sck = io(socket_url, {
                path: "/socket.io",
                autoConnect: true,
                extraHeaders: {
                    room: data.user.room,
                    user_id: data.user._id,
                    name: data.user.name
                }
            });
            sck.on('private', function(msg) {
                const message_object = {from: 'user', data: msg};
                setChatMessages([...chatMessages, message_object]);
            });
            setSocket(sck);
        }
        catch(exception){
            console.log("exception",exception);
        }
    }

    useEffect(() => {
      connect();
    }, []);

    const messageRef = useRef('');

    return (
        <div>
          <div>
              <div>
                  {
                      JSON.stringify(chatMessages)
                  }
              </div>
              <div>
                  <input ref={messageRef} type="text" placeholder="Enter message"/>
              </div>
              <div>
                  <button onClick={sendMessage} >Send</button>
              </div>
          </div>
        </div>
      )
}
测试现象

打开两个浏览器窗口分别登录user1和user2,user1发送三条消息:one、two、three,user1页面显示全部消息:

[{"from":"user1","data":"one"},{"from":"user1","data":"two"},{"from":"user1","data":"three"}]    

但user2页面仅显示最后一条消息:

[{"from":"user","data":"three"}]

若user1发送four,user2仅显示[{"from":"user","data":"four"}];当user2发送hello,user1也仅显示[{"from":"user","data":"hello"}]。

问题咨询

为何状态数组chatMessages会被重置?该问题仅出现在Socket的private事件回调函数中:

sck.on('private', function(msg) {
    const message_object = {from: 'user', data: msg};
    setChatMessages([...chatMessages, message_object]);
});    

原因分析

问题源于闭包捕获的状态值过时。connect函数在组件首次渲染时执行(依赖空数组的useEffect触发),此时sck.on('private')的回调捕获了chatMessages的初始值——空数组。后续组件重新渲染时,这个回调函数不会自动更新,每次触发private事件时,它依然用最初捕获的空数组拼接新消息,导致每次都只保留最新的一条消息,看起来像是数组被重置。

而sendMessage能正常工作,是因为它作为组件内部函数,每次组件渲染都会重新创建,捕获的是最新的chatMessages状态值。

解决方法

最简洁直接的修复方式是使用React的函数式状态更新:

sck.on('private', function(msg) {
    const message_object = {from: 'user', data: msg};
    // 传入函数,参数为当前最新的状态值
    setChatMessages(prevMessages => [...prevMessages, message_object]);
});

这种方式会让React自动传入当前最新的chatMessages状态,确保每次更新都基于最新的数组内容拼接新消息。


内容的提问来源于stack exchange,提问作者oderfla

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:45:31