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

React无限滚动Feed组件重复渲染问题及无需新Hook的解决方案

React无限滚动组件整体重渲染问题解决

问题描述

我是React新手,正在实现无限滚动功能,功能能正常运行,但每次加载新内容时,Feed组件都会因pageNum状态更新而整体重渲染。我使用了Intersection Observer,当滚动到页面底部时,通过自定义hook usePosts调用接口加载6条新数据。

自定义hook代码

const usePosts = (pageNum = 1) => {
  const [results, setResults] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [error, setError] = useState({});
  const [hasNextPage, setHasNextPage] = useState(false);
  const { category } = useArticlesContext();

  useEffect(() => {
    setIsLoading(true);
    setIsError(false);
    setError({});

    const controller = new AbortController();
    const { signal } = controller;
    getPostsPageArticles(category, pageNum, { signal })
      .then((data) => {
        console.log(data);
        setResults((prev) => [...prev, ...data]);
        setHasNextPage(Boolean(data.length));
        setIsLoading(false);
      })
      .catch((e) => {
        setIsLoading(false);
        if (signal.aborted) return;
        setIsError(true);
        setError({ message: e.message });
      });

    return () => controller.abort();
  }, [pageNum, category]);
  useEffect(() => {
    setResults([]);
  }, [category]);

  return { isLoading, isError, error, results, hasNextPage };
};

Feed组件代码

const Feed = () => {
  const [pageNum, setPageNum] = useState(1);

  const { isLoading, isError, error, results, hasNextPage } = usePosts(pageNum);
  const intObserver = useRef();

  const lastPostRef = useCallback(
    (post) => {
      if (isLoading) return;
      if (intObserver.current) intObserver.current.disconnect();

      intObserver.current = new IntersectionObserver((posts) => {
        if (posts[0].isIntersecting && hasNextPage) {
          console.log('We are near the last post');
          setPageNum((prev) => prev + 1);
        }
      });

      if (post) intObserver.current.observe(post);
    },
    [isLoading, hasNextPage],
  );

  if (isLoading) return <div>Loading...</div>;

  if (isError) return <div>Error: {error.message}</div>;

  const content = results.map((post, i) => {
    if (results.length === i + 1) {
      return <ArticleCard ref={lastPostRef} article={post} />;
    }
    return <ArticleCard article={post} />;
  });

  return (
    <div className="feed-container">
      {content}
      {isLoading && <p>Loading more posts...</p>}
    </div>
  );
};

ArticleCard组件代码

const ArticleCard = React.forwardRef(({ article }, ref) => {
  const articleCardBody = (
    <>
      <div className="article-card__title">
        <h2>{article.title}</h2>
      </div>

      <img src={article.img} alt="" className="article-card__img" />

      <div className="article-card__content">
        <p>{article.content}</p>
      </div>
    </>
  );

  const content = ref ? (
    <div className="article-card" ref={ref}>
      {articleCardBody}
    </div>
  ) : (
    <div className="article-card">{articleCardBody}</div>
  );
  return content;
});

解决方案

重渲染并非核心问题,问题出在Feed中文章的展示方式。正确做法是将文章列表处理逻辑封装为单独组件,并将文章加载逻辑放入独立Context中。

文章列表组件代码

const ArticleList = React.forwardRef(({ article }, ref) => {
  // 补充日期格式化逻辑(原代码未完整给出)
  const articleDate = new Date(article.date).toLocaleString();
  
  const cardItems = (
    <>
      <div className="article-card__title">
        <h2>{article.title}</h2>
      </div>

      <img
        src={`data:image/jpeg;base64,${article.img}`}
        alt=""
        className="article-card__img"
      />
      <div className="article-card__category">{article.category}</div>
      <div className="article-card__content">
        <p>{article.content}</p>
      </div>
      <div className="article-card__date">{articleDate}</div>
    </>
  );

  const content = ref ? (
    <div className="article-card" ref={ref}>
      {cardItems}
    </div>
  ) : (
    <div className="article-card">{cardItems}</div>
  );
  return content;
});

更新后的Feed组件代码

const Feed = () => {
  const {
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    data,
    status,
    error,
  } = useArticlesContext();

  const intObserver = useRef();
  const lastPostRef = useCallback(
    (post) => {
      if (isFetchingNextPage) return;
      if (intObserver.current) intObserver.current.disconnect();

      intObserver.current = new IntersectionObserver((posts) => {
        if (posts[0].isIntersecting && hasNextPage) {
          console.log('We are near the last post');
          fetchNextPage();
        }
      });

      if (post) intObserver.current.observe(post);
    },
    [isFetchingNextPage, fetchNextPage, hasNextPage],
  );

  if (status === 'error') return <div>Error: {error.message}</div>;
  const content = data?.pages.map((pg) => {
    return pg.map((post, i) => {
      if (pg.length === i + 1) {
        return <ArticleList ref={lastPostRef} article={post} key={post.url} />;
      }
      return <ArticleList article={post} key={post.url} />;
    });
  });

  return (
    <div className="feed-container">
      {content}
      {isFetchingNextPage && <p>Loading more posts...</p>}
    </div>
  );
};

另外,将文章加载逻辑迁移到了独立的Context中,统一管理分页数据和加载状态,避免Feed组件因内部状态更新导致的不必要重渲染。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 04:34:53