React列表滚动卡顿,如何优化移动端性能?
大家好,我在开发一个移动端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分,主要扣分在”消除重绘布局”和”减少渲染工作”。有没有什么移动端特有的优化方法?
先说原理吧。你现在是一次性把100个DOM节点全部渲染出来,每个节点里还有图片。移动端本来性能就捉襟见肘,浏览器要同时维护几百个DOM节点,还要处理图片加载、布局计算,不卡才怪。React.memo只能避免不必要的re-render,但解决不了"节点太多"这个根本问题。
虚拟列表的核心思路很简单:只渲染可视区域内的节点,滚出视野的就销毁或回收。不管你有一百条还是一万条,屏幕上永远只有那十几二十个DOM节点在跑。
推荐用react-window这个库,比react-virtualized轻量很多,API也更简洁。
注意那个style参数,react-window会自动注入位置信息,千万别覆盖掉,否则虚拟定位就废了。
然后说图片的问题。你的代码里img标签没有设置尺寸,图片加载完成前高度是0,加载后突然撑开,这就是典型的布局抖动。Lighthouse扣分点里的"消除重绘布局"主要就是这个。
解决方案是给图片容器设置固定的aspect-ratio,或者直接定死高度:
will-change这个属性要慎用,只给真正需要的元素加,滥用反而会更卡。列表项容器也可以加个transform: translateZ(0),强制开启硬件加速。
还有个容易忽略的点是触摸事件。移动端滚动时浏览器要处理touch事件,如果你列表项里有onClick,建议换成onTouchEnd或者用fastclick库,减少300ms延迟。不过现在很多浏览器已经优化掉了这个延迟,具体看你的目标设备。
另外检查一下你的CSS,有没有用box-shadow、gradient这种耗性能的属性?移动端GPU弱,这些效果能免则免。
如果上了虚拟列表还是有点卡,可以考虑进一步优化:图片用占位图先撑着,真正滚动停止了再加载高清图。react-window有个isScrolling参数可以用:
最后吐槽一句,React.memo和useCallback不是万能药。很多人一遇到性能问题就无脑加这两个,结果发现没用。性能优化得先定位瓶颈在哪,用React DevTools的Profiler跑一下,看看时间都花在哪了。你这个case明显是DOM节点太多导致的渲染压力,跟re-render关系不大。
上了虚拟列表之后,别说100条,几千条都能丝滑滚动。我之前做过一个5000条数据的列表,优化完跟原生滚动没区别。
图片加上 loading="lazy",这波操作下来 Lighthouse 能冲90+,搞定。