作为前端开发者,我们经常需要处理长列表数据的展示,而性能优化是其中关键的一环。本文将分享我在使用 antd-mobile 时整合虚拟列表(Virtual List)的实践经历,特别是遇到的坑和解决方案。
效果展示:
为什么需要 Virtual List
传统列表渲染会一次性渲染所有元素,当数据量很大时,会导致:
- 页面加载缓慢 2.内存占用高
- 滚动卡顿
- DOM 节点过多影响性能
虚拟列表通过仅渲染可视区域内的元素来解决这些问题,显著提升性能。
技术选型
尝试 react-virtuoso
我的第一个尝试是使用 react-virtuoso,一个现代化的虚拟列表库:
1 2 3
| import { Virtuoso } from "react-virtuoso";
<Virtuoso data={data} itemContent={(index) => <Item item={data[index]} />} />;
|
遇到的坑点:当与 antd-mobile 的PullToRefresh组件结合使用时,发现下拉刷新会”吞噬”列表的滚动事件,导致列表无法滚动。这可能是因为两者都在尝试控制滚动行为,产生了冲突。
切换到 react-virtualized
由于上述问题,我转向了更成熟的 react-virtualized 库,它提供了 List 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { List as VirtualList, AutoSizer, WindowScroller, } from "react-virtualized";
<WindowScroller> {({ height, scrollTop, isScrolling }) => ( <VirtualList autoHeight height={height} width={width} rowCount={data.length} rowHeight={120} rowRenderer={rowRender} scrollTop={scrollTop} isScrolling={isScrolling} /> )} </WindowScroller>;
|
关键实现点
配合 antd-mobile 的 PullToRefresh
要实现下拉刷新,需要将PullToRefresh包裹在虚拟列表外面:
1 2 3
| <PullToRefresh onRefresh={handleRefresh}> <VirtualList /* 属性 */ /> </PullToRefresh>
|
绝对定位的 Items
虚拟列表的核心在于高效重用 DOM 元素。在使用 react-virtualised 时,必须为每个列表项设置绝对定位:
1 2 3 4
| .list-item { position: absolute; top: ; }
|
踩坑经历:如果不使用绝对定位,当组件卸载时,高度会变小,导致后续项目渲染到屏幕之外,出现”跳跃”问题。
为了与页面其他元素协调滚动,需要使用 WindowScroller:
1 2 3 4 5
| <WindowScroller scrollElement={scrollElement}> {({ height, scrollTop, isScrolling }) => ( )} </WindowScroller>
|
完整代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import { InfiniteScroll, PullToRefresh } from "antd-mobile"; import { AutoSizer, List as VirtualList, WindowScroller, } from "react-virtualized";
export const ListComponent = () => { const scrollElm = useRef(null); const [element, setElement] = useState();
useEffect(() => { if (scrollElm.current) { setElement(scrollElm.current); } }, [scrollElm.current]);
const rowRender = ({ index }) => { const item = data[index]; return ( <div key={item.id} style={{ position: "absolute", top: `${index * 120}px`, left: 0, width: "100%", height: "120px", }} > <Item data={item} /> </div> ); };
return ( <div className="container" ref={scrollElm}> <WindowScroller scrollElement={element}> {({ height, scrollTop, isScrolling }) => ( <PullToRefresh onRefresh={handleRefresh}> <AutoSizer disableHeight> {({ width }) => ( <VirtualList autoHeight width={width} height={height} rowCount={data.length} rowHeight={120} rowRenderer={rowRender} overscanRowCount={5} isScrolling={isScrolling} scrollTop={scrollTop} /> )} </AutoSizer> </PullToRefresh> )} </WindowScroller> <InfiniteScroll loadMore={loadMore} hasMore={hasMore} /> </div> ); };
|
性能优化建议
- 批处理数据加载:结合
InfiniteScroll实现分页加载
- 固定高度元素:为虚拟列表中的元素使用固定高度
- 合理设置 overscan:避免渲染过多不可见元素
- 记忆化行渲染:使用
React.memo或useMemo优化行组件