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
相关产品推荐
相关产品推荐

