React列表滚动时性能分析显示大量重排怎么解决?

妍妍 Dev 阅读 30

我在用React开发无限加载列表时,发现滚动特别卡顿。用Chrome性能分析录屏后,发现Paint和Layout占比特别高,但列表组件已经用了useCallback和memo。

代码结构是这样的:


function InfiniteList({ items }) {
  const ref = useRef();
  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        onLoadMore(); // 这里会触发数据更新
      }
    });
    observer.observe(ref.current);
    return () => observer.disconnect(); // 我加了清理函数
  }, [items]); // 这里的依赖项对吗?

  return (
    <div>
      {items.map(item => (
        <div key={item.id} style={{ height: '100px' }}>
          {item.content}
        </div>
      ))}
      <div ref={ref}>加载更多</div>
    </div>
  );
}

尝试过在组件外层加

强制滚动容器,但效果不明显。性能面板里Layout每次滚动都触发,是不是因为高度计算的问题?应该怎么优化呢?

我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
一国玲
一国玲 Lv1
我也踩过这个坑,来直接说重点。

你遇到的问题十有八九是由于虚拟滚动没做导致的。即使你用了memo和useCallback,但列表长了以后,浏览器还是要计算每一个节点的位置,特别是你在滚动的时候频繁触发Layout。

我当时也卡在这,反复检查useEffect依赖项,结果发现根本不是React的问题,而是渲染的DOM节点太多了。

最直接的解决办法是引入虚拟滚动(Virtual Scrolling),只渲染可视区域附近的元素。react-window是我用过最好使的方案,它不会一次性渲染所有元素,而是只保留当前可视区域加上一点点缓冲区的节点,性能提升非常明显。

下面是你代码的一个改造版本,用的是react-window:

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (

{items[index].content}

);

function InfiniteList({ items }) {
// 你的IntersectionObserver逻辑可以保持不变
// 只是现在只需要关注可视区域内的节点

return (

height={window.innerHeight}
itemCount={items.length}
itemSize={100}
width="100%"
>
{Row}

加载更多


);
}


这样改完你会发现Layout的频率明显下降,Paint也轻松很多。

还有一个点:你useEffect的依赖项是items,但如果你每次加载更多数据时都更新items,那么useEffect会频繁执行,这会导致不断创建新的IntersectionObserver实例。你可以用ref来缓存数据长度,避免频繁触发observer.observe。

有问题再来问我。
点赞 5
2026-02-05 21:24