ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ant Design List 虚拟列表实战:基于 @rc-component/virtual-list 渲染无限长列表

Ant Design List 虚拟列表实战:基于 @rc-component/virtual-list 渲染无限长列表 Ant Design List 虚拟列表实战基于 rc-component/virtual-list 渲染无限长列表【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design在数据量大、单条行高固定、滚动频繁的列表场景下把全部 DOM 一次性挂载会导致首屏渲染与滚动性能急剧下降。Ant Design 官方在 List 组件的演示集components/list/demo/virtual-list.md中给出了标准解法不直接使用 List 自带的数据渲染能力而是把rc-component/virtual-list作为 List 的 children 注入结合滚动到底自动加载无限滚动实现无限长 虚拟化列表。阅读本文后你将掌握该 demo 的完整接入方式、滚动触底判断的精确写法、与纯无限加载方案的差异以及 List 组件在 antd 6.6.0 之后被 Listy 取代后虚拟滚动的迁移方向。一、场景定位为什么无限长与虚拟滚动必须一起出现先看官方 demo 的原始描述见 virtual-list.md中文说明结合 rc-component/virtual-list 实现滚动加载无限长列表能够提高数据量大时候长列表的性能。英文说明An example of infinite virtualized list via usingrc-component/virtual-list.关键词有两个无限长infinite数据源是不封顶、不断追加的用户滚到列表底部时自动拉取下一页数据。虚拟化virtualized无论数据累积到几千条还是几万条DOM 中永远只保留视口附近的一小批行滚动时通过计算可见窗口 复用节点来模拟完整列表。两者是互补关系无限加载负责数据不断变多虚拟滚动负责数据变多后 DOM 不跟着变多。只做无限加载而不同步做虚拟化列表仍会随着翻页越滚越卡只做虚拟化而没有无限加载则data全量传入时首屏仍然要遍历构造整份数据。该 demo 的参考场景是邮箱/会话记录/日志流这类数据只增不减、行高基本一致的列表。与本仓库其他加载更多 demo 的分工在 components/list/demo 目录下还提供了多个相邻方案便于对照理解loadmore.tsx经典的点击按钮加载更多infinite-load.tsx借助react-infinite-scroll-component实现的滚动自动加载无限加载virtual-list.tsx本文主角滚动自动加载 rc-component/virtual-list虚拟化。也就是说Ant Design 把长列表性能问题拆成三层能力呈现手动加载 → 无限滚动加载 → 无限滚动 虚拟化粒度依次递进。二、demo 源码逐段拆解容器、取数与触底检测演示文件是 components/list/demo/virtual-list.tsx下面按逻辑分块讲解其完整实现。1. 依赖与常量import React, { useEffect, useState } from react; import VirtualList from rc-component/virtual-list; import { Avatar, List, message } from antd; interface UserItem { email: string; gender: string; name: string; avatar: string; } const CONTAINER_HEIGHT 400; const PAGE_SIZE 20;要点rc-component/virtual-list是 antd 官方依赖生态rc-component/*系列中与 rc-virtual-list 同源的重命名包demo 直接以默认导出方式引入VirtualList。CONTAINER_HEIGHT 400同时作为两处关键数值一是虚拟滚动容器的固定高度二是滚动触底判定时距容器底部的允许误差边界两者必须保持一致。PAGE_SIZE 20决定每次滚动到底时向模拟接口请求的数据条数。2. 数据状态与模拟接口const App: React.FC () { const [messageApi, contextHolder] message.useMessage(); const [data, setData] useStateUserItem[]([]); const [page, setPage] useState(1); const appendData (showMessage true) { const fakeDataUrl https://660d2bd96ddfa2943b33731c.mockapi.io/api/users/?page${page}limit${PAGE_SIZE}; fetch(fakeDataUrl) .then((res) res.json()) .then((body) { const results Array.isArray(body) ? body : []; setData(data.concat(results)); setPage(page 1); showMessage messageApi.success(${results.length} more items loaded!); }) .catch(() { console.log(fetch mock data failed); }); }; useEffect(() { appendData(false); }, []);需要留意的几个实现细节首屏加载useEffect(() appendData(false), [])挂载后即拉取第一页且传入false关闭提示消息避免刚进入页面就弹出 20 more items loaded! 干扰体验之后每次滚动触底默认弹message提示加载结果。分页游标page状态在每次请求成功后自增fetch的 URL 使用pagelimit两参数从 mockapi.io 拉取分页数据。数据累积注意这里使用setData(data.concat(results))是在当前渲染闭包持有的data上拼接结果数组越滚越长——这正是需要虚拟化的原因。防御性处理Array.isArray(body) ? body : []用于容错失败时只console.log不阻塞后续滚动。3. 滚动触底的数学判定const onScroll (e: React.UIEventHTMLElement, UIEvent) { // Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions if ( Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - CONTAINER_HEIGHT) 1 ) { appendData(); } };这是 demo 中最值得抠细节的部分。判断是否滚动到底部不能直接用scrollHeight - scrollTop clientHeight因为部分浏览器/渲染环境下该差值会因滚动像素精度而出现亚像素误差。demo 采用Math.abs(...) 1的容差比较只要当前距容器底部的剩余距离小于等于 1px 即触发加载。Math.abs包裹后即使因滚动惯性轻微越过底部差值为负也能正确命中。对应参考的 MDN 说明指出读取scrollHeight应当使用requestAnimationFrame或延迟读取以避开布局抖动demo 的容差写法就是为了缓解这类边界问题。4. 组装 List VirtualListreturn ( {contextHolder} List VirtualList data{data} height{CONTAINER_HEIGHT} itemHeight{47} itemKeyemail onScroll{onScroll} {(item: UserItem) ( List.Item key{item.email} List.Item.Meta avatar{Avatar src{item.avatar} /} title{a hrefhttps://ant.design{item.name}/a} description{item.email} / divContent/div /List.Item )} /VirtualList /List / );这里采用了List 只当壳、VirtualList 负责滚动渲染的组合模式各层职责如下List不带dataSource、renderItem作为外层容器提供视觉框架与上下文。List 内部通过ListContext.Provider把grid、itemLayout下发给后代参见 components/list/index.tsx让List.Item仍能正常工作。VirtualList的data接收不断增长的data数组height为 400itemHeight为 47单行估计高度itemKeyemail用于标识每一行onScroll由上文的触底逻辑接管。VirtualList的 children 是一个渲染函数接收(item: UserItem)demo 在函数内复用 antd 的List.ItemList.Item.Meta组织行内容——也就是说行的高矮由List.Item.Meta的排版决定与itemHeight的估计值近似匹配。5. 为什么该 demo 被排除在快照测试之外在 components/list/tests/demo.test.ts 中可以看到demoTest(list, { skip: [virtual-list.tsx] });antd 会把大部分 demo 渲染进测试并做快照/交互校验但虚拟列表 demo 被显式跳过主要因为它依赖mockapi.io外部网络拉数测试环境无法保证数据返回的稳定性。这一点反过来提示使用者在真实项目中应把数据源替换为自己的分页接口或本地 mock并补充 loading/错误态。三、virtual-list 与 infinite-load两种滚动加载的真实差异同一个 demo 目录下的 infinite-load.tsx 很容易被误认为与 virtual-list 等价实际差异在于它们解决的是性能链路中的不同环节维度infinite-load.tsx无限滚动virtual-list.tsx虚拟滚动第三方库react-infinite-scroll-componentrc-component/virtual-list滚动容器自建div高度 400、overflow: auto通过scrollableTarget指定VirtualList 内部创建固定高度滚动容器触底触发InfiniteScroll的hasMore/next机制开发者手写scrollHeight - scrollTop - height 1判定数据量上限有dataLength 50的截断保护全量渲染无限追加但 DOM 只渲染可视窗口行渲染方式直接渲染List dataSource{...} /VirtualList 的 render prop 逐行产出List.Item结束态提供loaderSkeleton与endMessage无内置结束态依赖业务自行判断简言之infinite-load 适合数据有尽头、体量可控demo 用 50 条封顶的流式加载virtual-list 适合数据不断累积、体量可能上万的高密度滚动列表。若你的数据源是后者的形态应在做无限滚动的同时引入虚拟化二者并不互斥。四、与 rc-virtual-list 底层行为的对应关系rc-component/virtual-list是 React Component 系列rc-*维护的虚拟滚动容器。从 demo 的传参方式可以推断它的核心工作模型这有助于理解哪些参数会影响渲染结果height必传滚动视口的固定高度。只有视口内的行会被真实挂载这也是 demo 将触底判定与CONTAINER_HEIGHT绑定同一常量的原因——判定用到的可视高度必须与虚拟容器高度完全一致。itemHeight必传每行的估计高度用于在滚动发生时估算当前应该渲染哪一段数据以及需要预留多少占位高度。demo 中取 47 是因为默认尺寸List.ItemList.Item.Meta的排版高度大约如此若行的实际高度与itemHeight偏差过大会出现滚动跳动或空白区这是虚拟列表的固有约束也是它适合行高一致场景的原因。itemKey行的稳定唯一键。demo 传email与List.Item key{item.email}保持一致保证数据追加、滚动复用时 React 能正确 diff。children 为渲染函数(item, index) ReactNode与List的renderItem语义相近但作用对象是虚拟窗口内的一小批行而非整个数据源。onScroll虚拟列表滚动事件会冒泡到容器demo 借此在底部触发翻页。对照 List 自身的源码实现components/list/index.tsx可以看到List 在非 grid 模式下本会把dataSource映射为ul classNameant-list-items内的一批li。而 virtual-list demo 刻意不走这条全量渲染路径——它用VirtualList作为List的 children行节点实际由 rc 虚拟容器管理List仅保留最外层视觉与上下文。这正是该 demo 能同时享受antd 外观与rc 虚拟化的原因。五、注意List 已弃用长期方案是 Listy 的内置virtual在阅读该 demo 前应知晓一个现状List 组件本身已标记为废弃将在下一个大版本移除。这一事实有双处仓库证据components/list/index.tsx 在非生产环境会触发devUseWarning提示开发者从 6.6.0 起改用Listycomponents/list/index.en-US.md 的 FAQ 明确说明Listy 自antd6.6.0起作为 List 的继任者内置虚拟滚动、分组吸顶、程序化滚动等能力。因此对虚拟化长列表这一需求两条可选路线是沿用现有 List 生态当前版本可用继续按本文 demo 的方式组合rc-component/virtual-list适合不便升级或需要渐进迁移的场景代价是List会持续输出废弃告警。升级到 Listy推荐长期方案Listy 把虚拟滚动做成了原生能力。在 components/listy/index.en-US.md 的 API 中virtual属性即为是否开启虚拟滚动只渲染可视行需要配合height默认false自 6.6.0 起可用其 demo 目录下也提供了开箱即用的 components/listy/demo/virtual.tsx。关于从 List 迁移到 Listy 的映射详见 components/list/index.en-US.md与虚拟列表场景最相关的两条是dataSource→itemsrenderItem→itemRenderrowKey在 Listy 中为必填且不再默认取key字段若你依赖原默认值需显式传rowKeykey。大数据量场景不再需要任何第三方依赖给 Listy 设置virtualheight即获得虚拟滚动。也就是说本文拆解的List rc-component/virtual-list手动组合方案在未来语义上会被Listy virtual开关的零依赖方案收敛替代——理解前者高度、行高、itemKey、触底判定这些心智模型对熟练使用后者仍然完全适用。六、落地清单把该 demo 改造进真实项目综合前文从示例走向生产时的关键动作可归纳为替换数据源demo 中的mockapi.io分页接口仅为演示真实环境换成自己的后端分页接口并建议增加loading锁避免触底判定在请求未返回期间被连续触发造成重复请求对比 infinite-load.tsx 中if (loading) return;的防重入写法。保持行高一致维持固定的itemHeight避免在虚拟列表中使用会产生额外内边距/占位导致高度漂移的加载态如需骨架屏占位应放在触底加载区内而不是插入行内。统一高度常量把容器高度定义为一个常量同时传给VirtualList height和触底判定二者一旦不一致滚动触底将永远无法命中或过早命中。关注废弃迁移控制台出现 List 废弃告警属预期行为新代码可直接评估 Listy 的virtualheight方案减少一次从第三方库组合向内置能力的迁移成本。结语该 virtual-list demo 是 antd 文档中数据量大的长列表如何做性能优化的标准答案范本容器交给 antd List、渲染交给rc-component/virtual-list、取数交给业务分页接口、触底判定交给带 1px 容差的滚动差值计算。掌握它你既能把滚到几万条仍不卡顿的列表搬进现有 List 项目也能顺畅过渡到 Listy 内置virtual的新一代 API。【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表