React中WebSocket接收消息时状态数组不扩容?问题排查与解决
问题原因与解决方案
问题根源
你遇到的是React的闭包陷阱问题:
useEffect的依赖数组为空,意味着它只会在组件首次渲染时执行一次。此时socket.onmessage回调函数捕获的messages是初始的空数组状态。- 后续组件重新渲染时,这个回调函数并不会重新创建,依然保留着首次渲染时捕获的空数组引用。所以每次接收消息时,执行的都是
[...[], 新消息],导致数组永远只有1个元素,还会覆盖之前的内容。 - 而
addMessage函数每次组件渲染都会重新创建,能捕获到最新的messages状态,所以发送消息时可以正常追加。
解决方法
使用setMessages的函数式更新语法,直接基于最新的状态快照更新数组,彻底避免闭包带来的旧值问题:
修改后的关键代码
socket.onmessage = msg => { const parsedMessage = JSON.parse(msg.data) // 使用函数式更新,基于最新的prevMessages追加新消息 setMessages(prevMessages => [...prevMessages, {className: "other-message", body: parsedMessage.body}]) };
完整优化后的代码
import './App.css'; import { useState, useEffect } from "react" const socket = new WebSocket("ws://localhost:8080/ws"); function App() { const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState("") useEffect(() => { socket.onopen = () => { console.log("connected") } socket.onmessage = msg => { const parsedMessage = JSON.parse(msg.data) // 函数式更新,确保基于最新状态 setMessages(prevMessages => [...prevMessages, {className: "other-message", body: parsedMessage.body}]) }; socket.onclose = event => { console.log("Socket Closed Connection: ", event); }; socket.onerror = error => { console.log("Socket Error: ", error); }; }, []) const addMessage = () => { socket.send(inputValue); // 同样建议把发送消息的更新也改成函数式,避免快速点击等场景的状态丢失 setMessages(prevMessages => [...prevMessages, {className: "message", body: inputValue}]) setInputValue("") } console.log("messages", messages); return ( <div className="App"> <h1>Chat</h1> <div className='chat-window'> { messages.map((message, i) => { return <div key={i} className={message.className}>{message.body}</div> }) } </div> <div className='input-wrapper'> <input value={inputValue} onChange={e => setInputValue(e.target.value)}/> <button onClick={addMessage}>Send</button> </div> </div> ); } export default App;
为什么这样有效?
函数式更新会让React自动传递当前最新的状态快照(这里是prevMessages)给你,不管回调函数是何时创建的,都能拿到最准确的当前状态,彻底避开闭包陷阱导致的旧值引用问题。
内容的提问来源于stack exchange,提问作者Darien Miller
相关产品推荐
相关产品推荐

