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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 19:24:07