React列表滚动卡顿,如何优化移动端性能?

萌新.江洁 阅读 56

大家好,我在开发一个移动端React列表页时遇到性能问题。当列表项超过50条后滚动就明显卡顿,尝试用React.memo和useCallback优化过,但效果不明显…

代码结构大概是这样(简化版):


function ListItem({ item }) {
  return (
    <div className="item">
      <img src={item.image} alt="" />
      <p>{item.title}</p>
    </div>
  );
}

const List = () => {
  const [items, setItems] = useState(generateItems(100));
  
  return (
    <div className="list-container">
      {items.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </div>
  );
};

我检查过网络请求没问题,但真机测试时滑动还是有延迟。用Lighthouse测得分只有78分,主要扣分在”消除重绘布局”和”减少渲染工作”。有没有什么移动端特有的优化方法?

我来解答 赞 13 收藏
二维码
手机扫码查看
2 条解答
程序猿晓燕
这个问题的关键是你没有用虚拟列表。50条就卡顿,说实话问题挺严重的,单纯靠React.memo根本救不了。

先说原理吧。你现在是一次性把100个DOM节点全部渲染出来,每个节点里还有图片。移动端本来性能就捉襟见肘,浏览器要同时维护几百个DOM节点,还要处理图片加载、布局计算,不卡才怪。React.memo只能避免不必要的re-render,但解决不了"节点太多"这个根本问题。

虚拟列表的核心思路很简单:只渲染可视区域内的节点,滚出视野的就销毁或回收。不管你有一百条还是一万条,屏幕上永远只有那十几二十个DOM节点在跑。

推荐用react-window这个库,比react-virtualized轻量很多,API也更简洁。

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

// 单个列表项组件
const ListItem = ({ index, style, data }) => {
const item = data[index];
return (

src={item.image}
alt=""
loading="lazy" // 原生懒加载,双保险
style={{ width: '100%', height: 'auto' }}
/>

{item.title}



);
};

const VirtualizedList = () => {
const [items] = useState(generateItems(100));

return (
height={600} // 可视区域高度,根据实际情况调整
itemCount={items.length} // 总条数
itemSize={120} // 每项高度,动态高度用VariableSizeList
width="100%"
itemData={items} // 把数据传进去
>
{ListItem}

);
};


注意那个style参数,react-window会自动注入位置信息,千万别覆盖掉,否则虚拟定位就废了。

然后说图片的问题。你的代码里img标签没有设置尺寸,图片加载完成前高度是0,加载后突然撑开,这就是典型的布局抖动。Lighthouse扣分点里的"消除重绘布局"主要就是这个。

解决方案是给图片容器设置固定的aspect-ratio,或者直接定死高度:

.item img {
width: 100%;
height: 200px; /* 或者用 aspect-ratio: 16/9 */
object-fit: cover;
will-change: transform; /* GPU加速 */
}


will-change这个属性要慎用,只给真正需要的元素加,滥用反而会更卡。列表项容器也可以加个transform: translateZ(0),强制开启硬件加速。

还有个容易忽略的点是触摸事件。移动端滚动时浏览器要处理touch事件,如果你列表项里有onClick,建议换成onTouchEnd或者用fastclick库,减少300ms延迟。不过现在很多浏览器已经优化掉了这个延迟,具体看你的目标设备。

另外检查一下你的CSS,有没有用box-shadow、gradient这种耗性能的属性?移动端GPU弱,这些效果能免则免。

如果上了虚拟列表还是有点卡,可以考虑进一步优化:图片用占位图先撑着,真正滚动停止了再加载高清图。react-window有个isScrolling参数可以用:

const ListItem = ({ index, style, data, isScrolling }) => {
const item = data[index];

return (

{isScrolling ? (
// 滚动中显示占位
) : (

)}

{item.title}



);
};


最后吐槽一句,React.memo和useCallback不是万能药。很多人一遇到性能问题就无脑加这两个,结果发现没用。性能优化得先定位瓶颈在哪,用React DevTools的Profiler跑一下,看看时间都花在哪了。你这个case明显是DOM节点太多导致的渲染压力,跟re-render关系不大。

上了虚拟列表之后,别说100条,几千条都能丝滑滚动。我之前做过一个5000条数据的列表,优化完跟原生滚动没区别。
点赞 1
2026-03-01 15:23
司空广红
用虚拟列表,只渲染可视区域。装个 react-window,照着文档改一下你的 List 组件就行。

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

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

);

const VirtualizedList = () => (

{Row}

);


图片加上 loading="lazy",这波操作下来 Lighthouse 能冲90+,搞定。
点赞 18
2026-02-09 23:08