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

React+SignalR实时应用滚动加载重复渲染问题求助

滚动加载SignalR数据时重复渲染相同内容的问题分析

核心原因分析

1. SignalR连接重复创建与事件重复绑定

你的useEffect依赖数组包含loading,每次loading状态切换(比如从true变为false),这个useEffect就会重新执行:

  • 每次都会新建一个SignalR连接实例
  • 重复绑定ReceivePosts和SendPost事件
  • 旧连接没有被销毁,导致同一个GetPosts请求会被多个连接的事件监听处理,多次执行setPost([...prevPosts, ...post]),最终重复添加相同数据

2. Loading状态未及时锁定请求

在handleScroll触发分页请求前,没有先设置loading = true,导致滚动到页面底部时可能连续触发多次GetPosts调用,进一步加剧重复数据问题。

3. 分页参数与后端交互的潜在问题

如果后端的分页逻辑存在错误(比如page参数起始值处理不当,返回的分页数据与之前重复),也会导致前端重复渲染,但结合代码来看,前端连接重复是主要诱因。

修复方案

方案1:用useRef保存SignalR连接,避免重复创建

将连接实例存入useRef,确保整个组件生命周期内只创建一次连接:

export const Home = () => {
  const [post, setPost] = useState([]);
  const page = useRef(0);
  const [loading, setLoading] = useState(false);
  const connectionRef = useRef(null); // 保存连接实例

  useEffect(() => {
    // 只在组件挂载时创建一次连接
    if (!connectionRef.current) {
      const connection = new HubConnectionBuilder()
        .withUrl("http://localhost:5226/posts", {
          transport: HttpTransportType.WebSockets,
        })
        .build();

      connection.on("ReceivePosts", (newPosts) => {
        setPost((prevPosts) => [...prevPosts, ...newPosts]);
        setLoading(false);
      });

      connection.on("SendPost", (newPost) => {
        setPost((prev) => [...prev, newPost]); // 单条数据无需展开数组
        setLoading(false);
      });

      connection.start()
        .then(() => {
          connection.invoke("GetPosts", page.current, 5);
        })
        .catch((err) => console.log("Error: ", err.toString()));

      connectionRef.current = connection;
    }

    const handleScroll = () => {
      if (window.innerHeight + window.scrollY >= document.body.offsetHeight && !loading) {
        setLoading(true); // 先锁定loading,防止重复请求
        page.current += 1;
        connectionRef.current.invoke("GetPosts", page.current, 3)
          .catch((err) => {
            console.log("Error: ", err.toString());
            setLoading(false);
          });
      }
    };

    window.addEventListener("scroll", handleScroll);

    return () => {
      window.removeEventListener("scroll", handleScroll);
      // 组件卸载时断开连接
      if (connectionRef.current) {
        connectionRef.current.stop();
        connectionRef.current = null;
      }
    };
  }, [loading]);

  return (
    <>
      <div className="grid columns-10 justify-center row-auto gap-8 top-5">
        <div className="w-full ">
          <PostLayout>
            <div className="w-full flex flex-col gap-8 ">
              {loading ? (
                <div className="relative top-32">
                  <Loading />
                </div>
              ) : (
                post.map((prop) => {
                  return (
                    <Post
                      key={prop.id} // 用数据唯一ID代替index
                      author={prop.author}
                      text={prop.content}
                      url={prop.url}
                      image={prop.image}
                    />
                  );
                })
              )}
            </div>
          </PostLayout>
        </div>
      </div>
    </>
  );
};

方案2:修正Loading状态的时机

在触发GetPosts前立即设置loading = true,防止滚动事件多次触发请求,这步已包含在上述代码中。

方案3:避免用index作为列表key

原代码用index作为Post组件的key,当列表数据新增时,React可能错误复用组件导致渲染异常。改用每个post的唯一标识(比如数据库中的id字段)作为key,确保列表渲染的正确性。

内容的提问来源于stack exchange,提问作者Atalay Özyıldırım

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 20:43:30