ARTICLE DETAIL

资讯详情

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

cube-ui RecycleList 可回收列表组件:海量数据滚动渲染与无限滚动实战指南

cube-ui RecycleList 可回收列表组件:海量数据滚动渲染与无限滚动实战指南 前端UI组件移动开发【免费下载链接】cube-ui:large_orange_diamond: A fantastic mobile ui lib implement by Vue项目地址https://gitcode.com/gh_mirrors/cu/cube-ui点击查看免费下载cube-ui 的 RecycleList1.12.0 起提供是一款面向移动端的可回收滚动列表组件它通过把 DOM 数量始终控制在一个很低的范围让海量数据的列表渲染保持流畅。本文以官方文档为骨架结合仓库源码与示例完整讲解固定高度容器约束、默认滚动到底部加载与 Infinite 无限滚动两种交互、onFetchPromise 数据协议、四个核心 props、四个插槽以及reset实例方法并给出可直接运行的前后端mock示例代码。读完本文你将能够在自己的 Vue 移动端项目中落地一个高性能、可扩展的朋友圈式长列表。RecycleList 是 cube-ui 在 1.12.0 版本中新增的高性能列表组件核心思路是虚拟化渲染 增量拉取无论后端有多少数据页面中真正挂载的 DOM 始终只有可视区域附近的一小批 item其余数据只以坐标形式存在内存里。本文基于官方文档 recycle-list.md、组件源码 recycle-list.vue、模块入口 index.js、官方示例 recycle-list-default.vue 与 recycle-list-tombstone.vue以及单元测试 recycle-list.spec.js 展开讲解。一、使用前的三项注意事项文档在开篇就给出了三个决定组件能否正确运行的硬性约束理解它们比写模板本身更重要。1. 固定高度的父容器是无限滚动的前提RecycleList 自身在源码中被固定为height: 100%见 recycle-list.vue 的样式部分滚动区域是组件内部自带overflow-y: auto的容器。因此必须有一个固定高度的父元素包裹组件否则高度塌陷为 0滚动与位置计算都无从谈起。文档中的做法是给外层view-wrapper使用position: fixed撑满视口/* 限制视口 包裹组件 */ .view-wrapper position: fixed top: 54px left: 0 bottom: 0 width: 100%官方示例 recycle-list-default.vue 采用了position: absolutetop/left/bottom的写法效果等价只要保证最终计算出的高度是确定值即可。2. 不固定宽高的图片必须用样式占位组件通过预渲染测量高度来定位每个 item源码见下文高度测量与定位一节。如果 item 里有图片DOM 渲染完成时图片可能尚未加载此时测量的高度是错的。因此调用方必须给未加载的图片固定一个占位尺寸。示例的做法是让头像元素拥有固定的48px × 48px尺寸与background-image兜底图.avatar border-radius: 50% min-width: 48px width: 48px height: 48px background-image: url(./unknown.jpg) background-size: cover3. 内部实现依赖 Promise需要 polyfill组件的mounted钩子里显式调用了checkPromiseCompatibility()当window.Promise不存在时会输出警告信息requires a Promise polyfill in this browser.常量PROMISE_ERROR见 recycle-list.vue。此外加载流程中大量使用window.Promise.all等待每个 item 的高度测量完成所以必须为不支持 Promise 的旧浏览器引入 polyfill。二、默认模式滚动到底部自动加载下一页绝大多数列表交互是用户滚到底部 → 请求下一页。组件的默认实现正是围绕此交互设计的官方完整示例见 recycle-list-default.vue。1. 模板与数据协议div classview-wrapper cube-recycle-list classlist :sizesize :on-fetchonFetch :offsetoffset template slotitem slot-scope{ data } div :iddata.id classitem clickhandleClick(data) div classavatar :style{backgroundImage: url( (data.avatar || ) )}/div div classbubble p{{ data.msg }}/p div classmeta time classposted-date{{ data.time }}/time /div /div /div /template /cube-recycle-list /divexport default { data() { return { size: 50, offset: 100 } }, methods: { onFetch() { let items [] return new Promise((resolve) { // 模拟请求 50 条数据因为 size 设置为 50 setTimeout(() { for (let i 0; i 50; i) { items.push({ id: i, avatar: https://s3.amazonaws.com/uifaces/faces/twitter/danpliego/128.jpg, msg: 123, time: Thu Oct 25 2018 15:02:12 GMT0800 (中国标准时间) }) } resolve(items) }, 1000) }) }, handleClick(data) { console.log(Item: data) } } }这里有几个必须遵守的约定onFetch是必传属性源码中required: true且必须返回一个 Promise调用resolve时必须把items数组传入组件通过它拿到数据如果想停止滚动resolve 一个false即可若 resolve 的数组长度小于size组件也会自动判定没有更多数据停止继续拉取见源码getItems中res.length this.size的判断。2. 底层加载链路组件加载逻辑在 recycle-list.vue 的getItems()方法中实现每次调用onFetch()得到一个 Promisepush 进promiseStack同时loadings数组记录一次进行中Promise resolve 后若结果为false则调用stopScroll停止否则通过setList(index, res)把数据写入内部list按index * size定位批次随后loadItemsByIndex触发该批次 item 的高度测量与渲染滚动时_onScroll判断scrollTop offsetHeight heights - offset成立则再次调用load()拉取下一页容器 resize 时_onResize会把所有 item 标记为未加载并重新测量高度避免布局变化导致错位。在非 infinite 模式下组件底部还会渲染一个加载提示区域cube-recycle-list-loadingloading为真时可见默认内容是cube-loading组件。3. 通过作用域插槽拿到数据item是带作用域的插槽通过解构赋值slot-scope{ data }拿到 items 中的每一个数据项。注意示例中data可能是undefined首批数据未到位时组件会先以空对象占位因此模板里对data.avatar使用了(data.avatar || )的兜底写法。三、Infinite 模式用 tombstone 占位符实现无限滚动默认模式在等待数据时没有可见反馈。组件还提供了第二种交互——无限滚动模式开启后尚未加载数据的区域会先以tombstone墓碑占位符呈现让用户感知到这里即将有内容交互更友好、人性化。官方完整示例见 recycle-list-tombstone.vue。1. 模板结构div classview-wrapper cube-recycle-list classlist :infiniteinfinite :sizesize :on-fetchonFetch !-- tombstone 的作用域插槽 slot-scope 必须声明 -- template slottombstone slot-scopeprops div classitem tombstone div classavatar/div div classbubble p/p p/p p/p div classmeta time classposted-date/time /div /div /div /template template slotitem slot-scope{ data } div :iddata.id classitem clickhandleClick(data) div classavatar :style{backgroundImage: url( (data.avatar || ) )}/div div classbubble p{{ data.msg }}/p div classmeta time classposted-date{{ data.time }}/time /div /div /div /template !-- div slotspinnerLoading Data/div -- !-- div slotnoMore没有更多数据了/div -- /cube-recycle-list /div注意开启 infinite 后tombstone作用域插槽是必须实现的它是无限滚动模式下数据未到位时的占位视图。2. Infinite 模式的实现原理从源码看infinite 模式与默认模式最大的差异在load()方法默认模式只有在!this.loading无进行中的请求时才拉取数据infinite 模式会先把内部items数组的容量一次性扩充size个槽位items.length this.size每个空槽位通过setItem生成isTombstone: true的占位项随后再getItems()异步填充真实数据。模板层面每个可视 item 中同时渲染了tombstone与item两个插槽通过opacity与 500ms 的cube-recycle-list-transition过渡动画完成占位 → 真实内容的淡入切换div v-ifinfinite :style{opacity: !item.loaded} slot nametombstone/slot /div div :style{opacity: item.loaded} slot nameitem :dataitem.data/slot /div数据加载完成后removeUnusedTombs会从第一个仍然为 tombstone 的槽位处截断 items回收多余占位。需要强调无限滚动模式的自动停止条件与默认模式一致——onFetchresolvefalse或返回数组长度小于size二者都会触发stopScroll并将noMore置为true。3. spinner 与 noMore 插槽spinner加载更多时的提示视图默认是一个 loading 图cube-loadingnoMore无更多数据时的提示视图默认不渲染内容。官方示例中自定义为div slotnoMore没有更多数据了/div见 recycle-list-tombstone.vue。四、Props 配置一览官方文档给出了完整的 props 表格| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | infinite | 是否无限滚动模式 | Boolean | - | false | | size | 一次加载的数据量 | Number | - | 20 | | offset | 底部拉取更多数据的距离 | Number | - | 200 | | onFetch | 获取更多数据 | Function | 必传 | - |与源码 recycle-list.vue 中 props 定义对照后有两处细节值得留意offset默认值的差异文档表格写的是 200而当前仓库源码中为default: 100。两者语义一致——距底部还剩多少像素时触发下一次拉取实际使用时建议显式传入以消除歧义示例中即显式传了:offset100。onFetch的数据协议resolve 的第一个参数必须是数组或false。如果是数组且长度小于size组件会停止无限滚动如果是false同样停止。该约定在源码getItems中均有对应分支!res→stopScrollres.length this.size→stopScroll。TypeScript 类型声明 RecycleList.d.ts 中对应的签名是onFetch: Promiseany[] | falsereset: () void与文档描述完全一致。五、插槽与实例方法插槽| 名字 | 说明 | 参数 | | - | - | - | | tombstone | 实现个性化tombstone样式的作用域插槽 | - | | item | 列表某项的作用域插槽通过此插槽实现想要的渲染视图 | data: 调用方传入的数据项集合的某一项 | | spinner | 加载更多的提示文案的具名插槽 | - | | noMore | 无更多数据的提示文案的具名插槽 | - |使用注意infinite 模式下tombstone的slot-scope必须声明官方示例中声明为slot-scopepropsitem是唯一带数据参数的作用域插槽通过slot-scope{ data }解构。实例方法resetreset用于清空列表全部内容、重置数据。源码实现中它会将items、heights、startIndex、loadings、noMore、list、promiseStack七个内部状态全部重置并把容器scrollTop归零随后重新执行load()拉取首批数据。典型场景是下拉刷新或切换数据源后重新开始。六、虚拟化渲染原理从源码看低 DOM 数量如何实现文档强调组件始终将 DOM 的数量控制在一个很低的范围其关键在于源码中的三个机制可视窗口切片visibleItemsvisibleItems计算属性只渲染[startIndex - size, startIndex size]区间内的 item即最多2 * size个真实 DOM。滚动时_onScroll调用updateStartIndex根据scrollTop与每个 item 的top坐标二分定位新的起始索引实现窗口的滑动更新。绝对定位 translate 布局每个 item 都是position: absolute通过transform: translate(0, top)摆放到正确位置外层cube-recycle-list-items的高度由heights所有 item 高度累加撑起从而制造出整份列表的滚动条。预渲染高度池preloads pool模板底部的cube-recycle-list-pool区域隐藏渲染所有未测量高度的 itemupdateItemHeight在$nextTick后读取offsetHeight存入height随后updateItemTop依次累加计算每个 item 的top与总高度测量完成的 item 会从预渲染池移除。这也是前文图片必须固定宽高的原因——测量发生在图片加载完成之前不固定尺寸必然导致offsetHeight失真。单元测试 recycle-list.spec.js 从行为层面对这些机制做了验证render correct count data用例在滚动后断言items.length从 10 增长到 15滚动触发增量拉取should stop scroll用例断言 resolvefalse后noMore变为trueshould clean all contents用例验证reset()后list.length归零并重新加载。这些用例可直接作为你理解组件行为边界的参考。七、快速接入步骤安装在项目中安装 cube-ui当前仓库为组件库源码可直接参考 package.json 中的依赖与构建配置并按常规方式引入组件库注册RecycleList 的模块入口 index.js 通过Vue.component(RecycleList.name, RecycleList)完成全局注册注册后即可在模板中使用cube-recycle-list包一层固定高度的容器如本文第一部分所示用position: fixed/absolute或显式高度限定滚动区域实现onFetch返回一个 Promiseresolve 数组或false确保旧环境有 Promise polyfill编写插槽至少实现item作用域插槽infinite 模式下补全tombstone插槽按需自定义spinner与noMore处理图片对列表项中的图片预先固定占位尺寸需要重置时调用reset()组件会清空数据并重新从第一批开始加载。按此流程即可在移动端实现一条数据无限、DOM 有限的高性能长列表。赞分享前端UI组件移动开发【免费下载链接】cube-ui:large_orange_diamond: A fantastic mobile ui lib implement by Vue项目地址https://gitcode.com/gh_mirrors/cu/cube-ui点击查看免费下载相关推荐BetterScroll Infinity 插件海量列表数据下的无限滚动渲染方案BetterScroll Infinity 插件海量列表数据下的无限滚动渲染方案 infinity 插件为 BetterScroll 提供了无限滚动unli前端UI组件BetterScroll infinity 插件完全指南海量列表数据下的无限滚动渲染方案BetterScroll infinity 插件完全指南海量列表数据下的无限滚动渲染方案 导读 better scroll/infinity 是 Bette前端UI组件10万条数据丝滑滚动BetterScroll无限列表渲染实战指南10万条数据丝滑滚动BetterScroll无限列表渲染实战指南 你是否遇到过移动端列表滑动卡顿、白屏崩溃当数据超过1万条时传统渲染方式往往让页面如同老前端UI组件上一篇bert-base-turkish-cased-ner在商业应用中的价值ROI分析与成功案例下一篇Redux Thunk高级图表动画状态驱动过渡创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表