React实现消息列表滚动到底部时useEffect被多次触发怎么解决
问题原因&解决方案
核心问题定位
- 重复绑定WebSocket事件:WebSocket实例定义在组件外部,且每次组件重新渲染都会执行
createWebSocket方法,给同一个ws实例重复追加message事件监听。每次收到新消息时,多个绑定的回调会同时执行,多次更新messages数组,进而触发滚动逻辑多次执行。 - state更新存在闭包问题:
pushMessageToList中更新messages时直接依赖当前渲染周期的messages快照,多次触发更新时会拿到旧的state值,导致重复提交更新。 - 缺少组件销毁清理逻辑:组件卸载时没有移除WebSocket事件监听、关闭连接,会造成内存泄漏,也可能引发额外的更新触发。
修复步骤
1. 调整WebSocket初始化逻辑
将WebSocket的创建、事件绑定逻辑移到useEffect中,仅在组件挂载时执行一次,同时补充卸载时的清理逻辑。
2. 改用函数式更新state
修改setMessages的写法,用函数式更新直接拿到最新的state值,避免闭包依赖旧值。
3. 可选优化:滚动逻辑加触发判断
如果需要更顺滑的体验,可以判断当前用户是否处于列表底部,只有用户停在底部时才自动滚动,避免打扰用户手动浏览历史消息的操作。
修改后的完整代码
import { useState, useRef, useEffect } from 'react'; const Chat = () => { const [messages, setMessages] = useState([]); const messagesEndRef = useRef(); const wsRef = useRef(null); // 用ref存储WebSocket实例,避免重复创建 const createWebSocket = () => { wsRef.current = new WebSocket("ws://localhost:8080/chat"); wsRef.current.addEventListener("open", () => { console.log("We are connected!"); }); wsRef.current.addEventListener("message", (e) => { receiveMessage(e.data); }); }; const pushMessageToList = async (message, sentBy) => { const inputMessage = { id: Date.now(), message: message, sentBy: sentBy, }; // 函数式更新,直接获取最新的messages状态,避免闭包问题 setMessages(prevMessages => [...prevMessages, inputMessage]); }; const receiveMessage = (message) => { pushMessageToList(message, "chatbot"); }; const sendInputMessage = (message) => { pushMessageToList(message, "user"); wsRef.current?.send(message); }; const scrollToBottom = () => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); console.log("scroll"); } }; useEffect(() => { scrollToBottom(); }, [messages]); useEffect(() => { // 组件挂载时仅初始化一次WebSocket createWebSocket(); // 组件卸载时清理连接,避免内存泄漏 return () => { if (wsRef.current) { wsRef.current.close(); } }; }, []); // 空依赖数组,逻辑仅执行一次 return ( <div className="bg-white"> <div className="scroll"> <ListMessages listMessages={messages} /> <div ref={messagesEndRef} /> </div> <Input sendInputMessage={sendInputMessage} /> </div> ); }; export default Chat;
内容的提问来源于stack exchange,提问作者Herbie BN
相关产品推荐
相关产品推荐

