YAOTU INSIGHTS

Varlet List 组件详解:无限滚动触底加载的用法与源码级原理

Varlet List 组件详解:无限滚动触底加载的用法与源码级原理
UI组件前端【免费下载链接】varletA Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.项目地址https://gitcode.com/gh_mirrors/va/varlet点击查看免费下载var-list是 Varlet基于 Material Design 2/3 的 Vue 3 组件库中用于实现无限滚动列表的核心组件。它通过监听滚动容器实现触底自动加载支持加载、失败、加载完成三种状态的自定义展示并可与下拉刷新PullRefresh、标签页TabItem等组件联动。本文基于组件官方文档与仓库源码系统讲解其使用方式、状态管理协议与触底检测的底层实现。组件能力总览根据 组件文档var-list提供以下核心能力无限滚动加载检测到滚动容器滚动到底部时自动触发load事件手动位置检查通过实例方法check手动触发位置检测触底时触发load事件状态展示内置加载状态loading、错误状态error、数据加载完成状态finished的展示区域且均支持通过插槽或文本属性自定义。组件源码位于 packages/varlet-ui/src/list/入口文件 index.ts 通过withInstall注册组件并导出listProps供按需引入与默认值定制使用。基本使用load 事件与 loading 状态协议触底加载的核心交互协议是当检测到滚动容器滚动到底部时触发load事件组件会同时将loading置为true加载结束时必须手动将loading置回false本轮加载才算结束。这是防止重复触发的关键——loading为true期间组件不会再次触发加载见下文源码分析。script setup import { ref } from vue const loading ref(false) const finished ref(false) const list ref([]) function load() { setTimeout(() { for (let i 0; i 20; i) { list.value.push(list.value.length 1) } loading.value false if (list.value.length 60) { finished.value true } }, 1000) } /script template var-list :finishedfinished v-model:loadingloading loadload var-cell :keyitem v-foritem in list List Item: {{ item }} /var-cell /var-list /template要点说明v-model:loading双向绑定加载状态触发load时组件内部会自动将其置为truefinished为只读属性表示数据已全部加载完成置为true后组件停止触发load并展示finished-text文案官方示例代码与仓库内演示 example/index.vue 中load的实现逻辑一致每次追加 20 条数据累计到 60 条后标记finished。加载失败error 状态与点击重试当数据请求失败时可用v-model:error手动置位错误状态。组件会展示错误提示文案点击该提示会由组件自动将error复位为false并重新触发load事件从而实现点击重试的交互。script setup import { ref } from vue const loading ref(false) const error ref(false) const list ref([]) function load() { setTimeout(() { if (list.value.length 40) { error.value true loading.value false return } for (let i 0; i 20; i) { list.value.push(list.value.length 1) } loading.value false }, 1000) } /script template var-list v-model:errorerror v-model:loadingloading loadload var-cell :keyitem v-foritem in list List Item: {{ item }} /var-cell /var-list /template点击重试的实现位于 List.vueerror插槽的默认渲染节点绑定了clickload而load方法内部会先调用onUpdate:error(false)复位错误状态再调用onUpdate:loading(true)进入加载态最后触发onLoad// packages/varlet-ui/src/list/List.vue function load() { call(props[onUpdate:error], false) call(props[onUpdate:loading], true) call(props.onLoad) }这个顺序保证了无论处于何种状态重新发起加载时错误提示都会被清除。自定义状态文案loading-text / finished-text / error-text三种状态区域均有默认文案且都可以通过属性覆盖属性含义默认值英文 localeloading-text加载中文案Loadingfinished-text加载完成文案No moreerror-text加载失败文案Load fail默认文案并非写死在组件中而是来自 locale 文件 locale/en-US.ts 中的listLoadingText、listFinishedText、listErrorText三个词条中文环境默认为加载中 / 没有更多了 / 加载失败见 locale/zh-CN.ts。List.vue 中的渲染优先级为显式传入的loadingText ?? (pt ? pt : t)(listLoadingText)即属性 LocaleProvider 注入文案 全局 locale 默认文案因此接入 LocaleProvider 的项目可统一切换文案而无需逐个设置属性。script setup import { ref } from vue const loading ref(false) const finished ref(false) const list ref([]) function load() { setTimeout(() { for (let i 0; i 20; i) { list.value.push(list.value.length 1) } loading.value false if (list.value.length 60) { finished.value true } }, 1000) } /script template var-list loading-textloading QAQ finished-textfinished ORZ error-texterror TNT :finishedfinished v-model:loadingloading loadload var-cell :keyitem v-foritem in list List Item: {{ item }} /var-cell /var-list /template与 PullRefresh 组合上拉加载 下拉刷新将var-list嵌套在var-pull-refresh中即可同时获得上拉加载与下拉刷新两种交互注意保证PullRefresh容器的高度不为0否则下拉手势无法正确识别script setup import { ref } from vue const refreshing ref(false) const loading ref(false) const list ref([]) function refresh() { setTimeout(() { console.log(refresh) refreshing.value false }, 2000) } function load() { setTimeout(() { for (let i 0; i 20; i) { list.value.push(list.value.length 1) } loading.value false }, 1000) } /script template var-pull-refresh v-modelrefreshing refreshrefresh var-list v-model:loadingloading loadload var-cell :keyd v-ford in listListItem {{ d }}/var-cell /var-list /var-pull-refresh /template该组合在 PullRefresh 文档 的组合一节中同样被推荐为下拉刷新 无限滚动的标准方案。源码解析触底检测是如何实现的阅读 List.vue 可以完整还原触底加载的三条技术路径。1. 定位滚动容器getParentScroller组件挂载时onSmartMounted调用getParentScroller(listEl.value)向上遍历父节点找到最近一个overflow-y为scroll或auto的元素作为监听目标若一直找不到则回退为window。实现位于 utils/elements.tsexport function getParentScroller(el: HTMLElement): HTMLElement | Window { let element el while (element) { if (!element.parentNode) { break } element element.parentNode as HTMLElement if (element document.body || element document.documentElement) { break } const scrollRE /(scroll|auto)/ const { overflowY, overflow } getStyle(element) if (scrollRE.test(overflowY) || scrollRE.test(overflow)) { return element } } return window }因此var-list既可以放在页面级滚动window滚动中也可以放在局部滚动容器如固定高度 overflow-y: auto的面板中二者都能正确触发触底检测。组件卸载时通过onSmartUnmounted(removeScrollerListener)移除scroll监听避免内存泄漏。2. 触底判定detector 元素与 offset组件模板末尾始终渲染一个检测节点div :classn(detector)配合 list.less 中width: 100%的零高度样式它始终位于列表内容的最底部。触底判定逻辑如下// packages/varlet-ui/src/list/List.vue function isReachBottom() { const { bottom: containerBottom } getRect(scroller) const { bottom: detectorBottom } getRect(detectorEl.value!) // The fractional part of the detectorBottom when bottoming out overflows return Math.floor(detectorBottom) - toPxNum(props.offset) containerBottom }其判定条件是detector 的可视区底部坐标减去offset后不超过滚动容器的可视区底部坐标。源码注释特意指出Math.floor的存在是修复触底时底部坐标溢出小数部分的边界问题——即浮点误差可能让判定多触发一次向下取整可消除该偏差。关于offset其类型是string | number默认0表示距离底部多远时提前触发加载预加载距离。从源码看字符串值会经过 toPxNum 统一换算支持10、10、10px以及vw/vh/rem等单位因此传入offset200可实现在距底部 200px 时提前加载下一页。3. 检查的触发时机与互斥守卫check是位置检查的入口也是组件实例暴露的方法存在三类触发来源// packages/varlet-ui/src/list/List.vue watch(() [props.loading, props.error, props.finished], check) onSmartMounted(() { scroller getParentScroller(listEl.value!) scroller.addEventListener(scroll, check) if (props.immediateCheck) { check() } }) async function check() { await nextTick() if (props.loading || props.finished || props.error || tabItem?.current.value false || !isReachBottom()) { return } load() }滚动监听滚动容器每次scroll事件都会调用check无节流依赖守卫条件短路初始化检查immediate-check默认true挂载后立即check一次——若首屏内容不足一屏初始即触底会立刻触发首次加载避免用户拉不动状态变化重检loading/error/finished任一变化都会重新check。例如加载完成追加数据后若仍触底会自动继续加载下一页。守卫条件loading || finished || error || tabItem?.current.value false || !isReachBottom()保证了同一时间只有一个进行中的加载loading互斥加载完成finished后不再响应任何触底处于错误态时等待用户点击重试而不是静默重发嵌套在TabItem中且该页签不可见时不加载见下节。check内部await nextTick()后再取坐标确保在 DOM 更新如新数据渲染、状态区显隐之后再计算避免基于过期布局误判。与 TabItem 联动的隐藏机制从源码结构看var-list与var-tab-item存在内置联动list/provide.ts 通过useTabItem从父级TabItem注入 provider并在 List.vue 中监听tabItem.currentconst { tabItem, bindTabItem } useTabItem() if (tabItem) { watch(() tabItem.current.value, check) }这意味着当var-list位于var-tabs-items的某个var-tab-item内时隐藏页签中的列表不会触发加载守卫条件tabItem?.current.value false切换到该页签时current变为true会自动check一次若已触底则加载。测试用例 index.spec.js 的load event while list in tab-item验证了这一行为初始状态下只有激活页签的load被调用一次切换到第二个页签后其load才被触发。使用var-tabs-itemsvar-tab-item承载多个分页列表时这一机制无需任何额外配置即可生效。使用注意滚动容器的识别陷阱官方文档Be Careful一节指出了一条重要的 CSS 边界问题触底检测依赖监听滚动容器的 scroll 事件。滚动容器是指overflow-y为auto或scroll的最近元素。当把某元素的overflow-x设置为非visible的值时浏览器会将该元素的overflow-y自动固化为auto以维持 BFC 结构——这会被误识别为滚动容器应避免。对照 getParentScroller 的实现可以确认它只检查overflow-y和overflow两个计算样式/(scroll|auto)/命中即返回。因此若列表外层存在一个overflow-x: hidden浏览器会把overflow-y计算为auto的包裹元素且该元素出现在body之前var-list就会把它的父级而非真正的滚动目标当作 scroller导致触底检测失效。实践建议在列表到body的路径上避免出现非visible的overflow-x声明。API 参考PropsProp说明类型默认值v-model:loading加载状态booleanfalsev-model:error错误状态booleanfalseimmediate-check初始化时是否立即进行位置检测booleantruefinished是否加载完成booleanfalseoffset距底部触发距离string | number0loading-text加载中文案stringLoadingfinished-text加载完成文案stringNo moreerror-text加载失败文案stringLoad fail默认值以 props.ts 源码为准immediateCheck默认true、offset默认0其余布尔状态默认false文案默认值来自 locale/en-US.ts。Methods方法说明参数返回值check触发位置检查触底时触发load事件--check通过setup的返回值暴露在组件实例上List.vue适合在数据手动更新、路由切换后强制重检一次是否触底。Events事件说明参数load触底时触发-Slots插槽名说明插槽参数default列表内容-loading加载中内容-error加载失败内容-finished加载完毕内容-四个插槽在 List.vue 模板 中均为条件渲染loading为真时渲染loading插槽默认内容是文案 var-loading迷你转圈finished/error同理。需要完全自定义状态区 UI例如替换为骨架屏或自定义按钮时使用对应命名插槽即可。样式变量组件样式定义在 list.less通过 CSS 变量控制三个状态区的外观可配合 StyleProvider 组件 定制变量名默认值--list-loading-height50px--list-finished-height50px--list-error-height50px--list-loading-color#888--list-finished-color#888--list-error-color#888--list-loading-font-sizevar(--font-size-md)--list-finished-font-sizevar(--font-size-md)--list-error-font-sizevar(--font-size-md)例如将加载区调矮并换色:root { --list-loading-height: 40px; --list-loading-color: var(--color-primary); }测试用例对行为的佐证单元测试tests/index.spec.js 从三个层面验证了上述机制触底触发mockgetBoundingClientRect返回bottom: 0即 detector 已触底后onLoad与onUpdate:loading各被调用 1 次验证了触底 → 自动置 loading → 触发 load的完整协议三种状态区分别以loading/error/finished为真时渲染对应的.var-list__loading/.var-list__error/.var-list__finished节点并展示自定义文案置回false后节点移除TabItem 联动两个页签各含一个var-list仅激活页签的列表触发加载切换页签后新页签列表才触发加载。小结var-list的用法可以浓缩为一条状态协议监听load事件发起请求 → 请求结束置loading false→ 数据耗尽置finished true→ 请求异常置error true并等待点击重试。理解其源码实现后还有三点值得记住滚动容器通过overflow-y自动向上查找、触底判定依赖底部的零高度 detector 元素与offset预加载距离、overflow-x的非visible值会污染滚动容器识别。掌握这些细节可以在移动端信息流、分页接口、多页签列表等场景下稳定落地无限滚动加载。赞分享UI组件前端【免费下载链接】varletA Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.项目地址https://gitcode.com/gh_mirrors/va/varlet点击查看免费下载相关推荐Naive UI 无限滚动 Infinite Scroll 组件实战指南滚动到底自动加载的实现原理与参数详解Naive UI 无限滚动 Infinite Scroll 组件实战指南滚动到底自动加载的实现原理与参数详解 导读 本文围绕 Naive UI 组件库中的 n前端UI组件Naive UI 无限滚动组件 Infinite Scroll 实战指南props 用法与源码原理Naive UI 无限滚动组件 Infinite Scroll 实战指南props 用法与源码原理 自 Naive UI 2.38.2 版本起组件库内置了前端UI组件javascript.info 无限滚动页面实战用 getBoundingClientRect 与 scroll 事件实现触底自动加载javascript.info 无限滚动页面实战用 getBoundingClientRect 与 scroll 事件实现触底自动加载 无限滚动endles文档/教程前端上一篇OpenNI API完全手册开发者必备的函数与接口速查指南下一篇JS The Right Way依赖管理npm与yarn的最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考