React中useEffect内设置state后Socket监听访问报错问题
问题分析与解决方案
错误原因
首次加载应用时,axios的异步请求还未完成,records状态仍为初始值(通常是空数组)。此时若收到socket推送的新消息,records.find(e=>e.id===msg.conversationId)会返回undefined,后续访问result.Messages就会抛出Cannot read properties of undefined (reading 'Messages')错误。
解决办法
1. 增加空值判断,处理未找到会话的情况
在处理会话记录前,先检查是否找到对应会话,若未找到可直接创建新会话加入列表:
newsocket.on("socketListening", (msg) => { console.log(msg) try{ setRecords(prevRecords => { const result = prevRecords.find(e=>e.id===msg.conversationId) // 未找到对应会话,创建新会话对象 if(!result){ const newConversation = { id: msg.conversationId, Messages: [msg], lastMessage: msg.message, Recipient: msg.senderId !== ADMIN.id } return [newConversation, ...prevRecords] } // 找到会话,更新消息和会话信息 const updatedResult = { ...result, Messages: [msg, ...result.Messages], lastMessage: msg.message, Recipient: msg.senderId !== ADMIN.id } return [updatedResult, ...prevRecords.filter(e=>e.id!==msg.conversationId)] }) }catch(e){ console.log(e) } })
2. 使用函数式更新获取最新状态
因为useEffect依赖为空数组,原回调中的records是初始值的闭包,无法获取到后续更新的状态。用setRecords(prevRecords => { ... })的函数式写法,能直接拿到最新的状态值,避免闭包导致的旧值问题。
3. 避免直接修改原状态对象
原代码中直接修改result.Messages=newMessages会直接篡改状态中的对象,可能导致React无法正确检测状态变化。通过扩展运算符...result创建新对象,保证状态更新的可追踪性。
可选:等待会话列表加载完成后再初始化socket监听
如果业务允许,可以把socket监听逻辑放到axios请求的then回调中,确保只有当会话列表加载完成后才处理socket消息:
useEffect(()=> { let newsocket; axios .get('/api/messages/',{headers:{"auth-token":localStorage.getItem('jwtToken')}}) .then(res => { setRecords(res.data) // 会话加载完成后初始化socket newsocket = io.connect(CONNECTION_PORT) newsocket.on('connect', msg => { setSocket(newsocket) console.log(`Admin ${ADMIN.id} is waiting. socketID: ${newsocket.id}`) }); newsocket.emit('waiting', ADMIN.id); newsocket.on("socketListening", (msg) => { setRecords(prevRecords => { const result = prevRecords.find(e=>e.id===msg.conversationId) if(!result){ const newConversation = { id: msg.conversationId, Messages: [msg], lastMessage: msg.message, Recipient: msg.senderId !== ADMIN.id } return [newConversation, ...prevRecords] } const updatedResult = { ...result, Messages: [msg, ...result.Messages], lastMessage: msg.message, Recipient: msg.senderId !== ADMIN.id } return [updatedResult, ...prevRecords.filter(e=>e.id!==msg.conversationId)] }) }) }) .catch(err => console.log(err) ) return(()=>{ if(newsocket) newsocket.close(); }); },[]);
注意:这种方式可能会丢失axios请求完成前的socket消息,需根据业务场景选择。
内容的提问来源于stack exchange,提问作者kd12345
相关产品推荐
相关产品推荐

