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

React自定义懒加载Hook触发无限循环问题求助

解决自定义useLazyFetch Hook的无限循环问题

核心问题分析

调用fetchApi()后,Redux状态更新触发UI重新渲染,导致带loadMoreRef的跟踪元素回到视口顶部,再次触发Intersection Observer的回调,进而重复调用fetchApi(),形成循环。以下是针对性的解决方法:


1. 增加请求状态锁,阻止重复触发

在useLazyFetch Hook中维护加载状态和是否还有更多数据的标记,只有当未在加载且还有数据可加载时,才允许触发请求:

import { useState, useEffect, useRef } from 'react';

function useLazyFetch(fetchApi, hasMore) {
  const [isLoading, setIsLoading] = useState(false);
  const observerRef = useRef(null);

  const fetchData = async () => {
    if (isLoading || !hasMore) return;
    setIsLoading(true);
    try {
      await fetchApi();
    } catch (err) {
      // 处理请求错误
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        if (entry.isIntersecting && !isLoading && hasMore) {
          console.log('INTERSECTING...');
          fetchData();
        }
      },
      { rootMargin: '0px 0px 100px 0px' } // 可根据需求调整触发提前量
    );

    if (observerRef.current) {
      observer.observe(observerRef.current);
    }

    return () => {
      if (observerRef.current) {
        observer.unobserve(observerRef.current);
      }
    };
  }, [isLoading, hasMore, fetchData]);

  return { observerRef, isLoading };
}
  • 关键:通过isLoading阻止并发请求,hasMore需在数据加载完毕后设为false(比如返回的帖子数量小于每页条数时)。

2. 确保跟踪元素稳定,避免重复挂载

把带loadMoreRef的跟踪div放在列表外部(比如列表容器的最底部),避免因列表项更新导致元素被重新创建或位置异常:

function Home() {
  const { posts, hasMore } = useSelector(state => state.posts);
  const dispatch = useDispatch();
  const { observerRef, isLoading } = useLazyFetch(
    () => dispatch(fetchPosts()), // fetchPosts为Redux切片的异步action
    hasMore
  );

  return (
    <div className="post-list-container">
      {posts.map(post => (
        <PostItem key={post.id} post={post} />
      ))}
      {/* 跟踪元素独立于列表项,放在列表末尾 */}
      {hasMore && (
        <div ref={observerRef} className="load-more-trigger" style={{ minHeight: '40px' }}>
          {isLoading ? '加载中...' : '滚动加载更多'}
        </div>
      )}
    </div>
  );
}
  • 注意:给跟踪元素设置固定最小高度,避免因内容变化导致位置跳变。

3. 稳定fetchApi的引用,避免Hook频繁重执行

如果fetchApi是在组件内定义的函数,用useCallback包裹它,确保引用稳定,防止useLazyFetch内的useEffect频繁重新执行:

function Home() {
  // ...
  const fetchPosts = useCallback(() => {
    return dispatch(getPosts()); // getPosts为Redux异步action
  }, [dispatch]);

  const { observerRef, isLoading } = useLazyFetch(fetchPosts, hasMore);
  // ...
}

4. 保留滚动位置,防止UI重渲染后滚动重置

如果Redux状态更新导致组件重新渲染时滚动位置丢失,用useRef记录滚动容器的滚动位置,在渲染后恢复:

function Home() {
  const containerRef = useRef(null);
  const scrollTopRef = useRef(0);

  // 保存滚动位置
  useEffect(() => {
    const container = containerRef.current;
    const handleScroll = () => {
      scrollTopRef.current = container.scrollTop;
    };
    container?.addEventListener('scroll', handleScroll);
    return () => container?.removeEventListener('scroll', handleScroll);
  }, []);

  // 帖子更新后恢复滚动位置
  useEffect(() => {
    containerRef.current?.scrollTo(0, scrollTopRef.current);
  }, [posts]);

  return (
    <div ref={containerRef} className="post-list-container">
      {/* 列表和跟踪元素 */}
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Saurabh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:15:41