当前位置: 首页 > news >正文

虚拟列表组件如果滑动速度过快导致渲染性能问题

列举 antdesign 中的 虚拟列表组件VirtualListList

1. Ant Design 的 VirtualList 简介

Ant Design 提供的虚拟列表组件(如 VirtualList)基于虚拟滚动技术,适用于展示大量数据的场景。它通过只渲染可视区域的元素,大幅减少 DOM 节点数量,提高性能。

常见问题

  • 当用户快速滚动时,可能出现:
    • 白屏问题:渲染逻辑跟不上滚动速度。
    • 卡顿问题:滚动事件触发过于频繁,导致主线程阻塞。

解决方向

  1. 减少滚动触发频率:通过节流降低滚动事件的触发频率。
  2. 增加缓冲区:提前渲染可视区域上下的数据,避免白屏。
  3. 分片渲染:将渲染任务拆分成小块,避免主线程阻塞。
  4. 调整组件属性:优化 VirtualList 或 List 的配置。

2. 调整 Ant Design 虚拟列表的配置

Ant Design 的虚拟列表支持一些关键配置,以下是常用优化方法:

import React from 'react';
import { List } from 'antd';
import VirtualList from 'rc-virtual-list';const data = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);const MyVirtualList = () => {const itemHeight = 50; // 每项的固定高度const containerHeight = 500; // 容器高度return (<List><VirtualListdata={data}height={containerHeight} // 容器高度itemHeight={itemHeight}  // 每项高度itemKey="id"             // 唯一标识onScroll={(e) => {console.log('scrolling', e.currentTarget.scrollTop);}}>{(item) => <List.Item>{item}</List.Item>}</VirtualList></List>);
};export default MyVirtualList;

关键配置说明

  • height: 设置容器的高度,确保虚拟滚动生效。
  • itemHeight: 每个列表项的固定高度,虚拟列表性能依赖于此值。
  • itemKey: 设置唯一标识,减少 DOM diff 的开销。
  • onScroll: 监听滚动事件,用于调试或自定义滚动逻辑。

3. 针对快速滚动的优化策略

(1) 增加缓冲区(当滚动速度过快时,虚拟列表可能只渲染当前可视区域的内容,导致白屏)

  • 解决方案
    Ant Design 虚拟列表支持 缓冲区(buffer),通过 VirtualList 的 height 和 itemHeight 动态调整缓冲区大小。

    <VirtualListdata={data}height={containerHeight} // 容器高度itemHeight={itemHeight}  // 每项高度itemKey="id"             // 唯一标识// 增加缓冲区onScroll={(e) => {console.log('scrolling', e.currentTarget.scrollTop);}}
    >{(item) => <List.Item>{item}</List.Item>}
    </VirtualList>
    

(2) 滚动事件的节流(滚动事件触发频率过高,导致主线程被占用)

  • 解决方案
    对滚动事件进行 节流 处理,降低触发频率

    import { throttle } from 'lodash';const handleScroll = throttle((e) => {console.log('Scrolled:', e.currentTarget.scrollTop);
    }, 16); // 每 16ms 触发一次<VirtualListdata={data}height={containerHeight}itemHeight={itemHeight}itemKey="id"onScroll={handleScroll} // 使用节流处理滚动事件
    >{(item) => <List.Item>{item}</List.Item>}
    </VirtualList>
    

(3) 分片渲染(渲染任务太重,滚动时主线程被阻塞)

  • 解决方案
    将渲染任务拆分为小块,分批完成。

    import React, { useState, useEffect } from 'react';
    import { List } from 'antd';const MyVirtualList = () => {const [renderedData, setRenderedData] = useState([]);const data = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);useEffect(() => {let start = 0;const chunkSize = 100; // 每次渲染 100 个const interval = setInterval(() => {if (start >= data.length) {clearInterval(interval); // 渲染完成return;}setRenderedData((prev) => [...prev, ...data.slice(start, start + chunkSize)]);start += chunkSize;}, 50); // 每 50ms 渲染一次}, []);return (<ListdataSource={renderedData}renderItem={(item) => <List.Item>{item}</List.Item>}/>);
    };export default MyVirtualList;
    

(4) 动态加载数据(一次性加载所有数据可能导致内存占用过高)

  • 解决方案
    使用分页或动态加载策略,只加载当前可视区域及其缓冲区的数据。

    const MyVirtualList = () => {const [data, setData] = useState([]);const [page, setPage] = useState(1);useEffect(() => {// 模拟异步加载数据const fetchData = async () => {const newData = Array.from({ length: 100 }, (_, i) => `Item ${i + (page - 1) * 100}`);setData((prev) => [...prev, ...newData]);};fetchData();}, [page]);const handleScroll = (e) => {const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;if (scrollTop + clientHeight >= scrollHeight - 50) {setPage((prev) => prev + 1); // 加载下一页}};return (<List><VirtualListdata={data}height={500}itemHeight={50}itemKey="id"onScroll={handleScroll}>{(item) => <List.Item>{item}</List.Item>}</VirtualList></List>);
    };
    

4. 使用 Intersection Observer

通过 Intersection Observer 动态检测哪些元素进入了可视区域,只在需要时渲染这些元素

import React, { useRef, useEffect } from 'react';const MyVirtualList = ({ items }) => {const observer = useRef();useEffect(() => {observer.current = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {const index = entry.target.dataset.index;entry.target.textContent = `Item ${index}`;}});});const elements = document.querySelectorAll('.list-item');elements.forEach((el) => observer.current.observe(el));return () => observer.current.disconnect();}, []);return (<div style={{ height: '500px', overflow: 'auto' }}>{items.map((_, index) => (<div key={index} data-index={index} className="list-item" style={{ height: '50px' }}>Loading...</div>))}</div>);
};export default MyVirtualList;

5. 总结

在这里插入图片描述

http://www.lryc.cn/news/588273.html

相关文章:

  • UART寄存器介绍
  • 前端学习5:Float学习(仅简单了解,引出flex)
  • 015 程序地址空间入门
  • Life:Internship in OnSea Day 22
  • 某ctv视频下载逆向思路
  • 云原生技术与应用-Containerd容器技术详解
  • Git LFS 操作处理Github上传大文件操作记录
  • Spring Boot 双数据源配置
  • RPC vs RESTful架构选择背后的技术博弈
  • SecretFlow 隐语 (2) --- 隐语架构概览
  • 【安卓笔记】进程和线程的基础知识
  • Ubuntu20.05上安装Clang 15
  • 【安卓笔记】线程基本使用:锁、锁案例
  • 新型eSIM攻击技术可克隆用户资料并劫持手机身份
  • linux 内核: 访问当前进程的 task_struct
  • [论文阅读] 人工智能 + 软件工程 | 用大语言模型+排名机制,让代码评论自动更新更靠谱
  • android Perfetto cpu分析教程及案例
  • 迁移学习之图像预训练理解
  • ICML 2025 | 从语言到视觉,自回归模型VARSR开启图像超分新范式
  • C# TCP粘包与拆包深度了解
  • CSP-S 模拟赛 17
  • 哈希扩展 --- 海量数据处理
  • 一文明白AI、AIGC、LLM、GPT、Agent、workFlow、MCP、RAG概念与关系
  • Linux操作系统从入门到实战(七)详细讲解编辑器Vim
  • 螺旋模型:风险分析驱动的渐进式开发
  • C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
  • 什么是实时数仓?实时数仓又有哪些应用场景?
  • 疯狂星期四 - 第7天运营日报
  • 多线程/协程环境时间获取的“时间片陷阱“:深度解析与工程级解决方案
  • 16.避免使用裸 except