Vue 3 中列表虚拟化可与下拉刷新、上拉加载协同工作,需采用三层结构:外层 PullRefresh、中间自定义滚动容器、内层虚拟滚动组件,并明确事件分工、状态分层与移动端兼容处理。

Vue 3 中列表虚拟化与下拉刷新、上拉加载更多不是互斥功能,而是可以协同工作的三层结构:虚拟滚动负责渲染性能,PullRefresh 负责顶部刷新交互,List 组件或滚动监听负责底部加载触发。关键在于容器层级关系、事件隔离和状态同步。
用 Vant 的 PullRefresh + List 搭配虚拟滚动容器
直接将 <van-pull-refresh> 包裹 <van-list> 是常见做法,但若想接入虚拟滚动(如 vue-virtual-scroller),不能让三者嵌套混乱。正确结构是:
- 外层用
<van-pull-refresh>—— 它必须是滚动容器的直接父级,才能捕获 touch 事件并控制下拉动画 - 中间层是自定义滚动容器(如带
ref的<div class="scroller">),设overflow-y: auto并启用-webkit-overflow-scrolling: touch - 内层用
<RecycleScroller>或<DynamicScroller>,其items和item-size需动态计算,且scroller属性要指向中间层容器 ref
滚动事件与触摸事件的分工要明确
下拉刷新依赖 touchstart/touchmove/touchend,上拉加载依赖 scroll,二者不能互相干扰:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
- 在
touchstart时只检查scrollTop === 0,才记录起始 Y;否则忽略,避免在列表中部误触发 -
touchmove中仅当处于顶部且向下拖动时调用e.preventDefault(),防止 iOS 弹性滚动干扰 -
scroll事件交给虚拟滚动容器监听,用节流处理(如 100ms 一次),判断是否触底:scrollTop + clientHeight >= scrollHeight - 30
数据流与 loading 状态需分层管理
虚拟滚动本身不管理加载状态,需由业务逻辑统一协调:
立即学习“前端免费学习笔记(深入)”;
- 定义三个状态变量:
refreshing(下拉中)、loadingMore(上拉中)、finished(无更多) - 下拉刷新时清空
items并重置page = 1,成功后设refreshing = false - 上拉加载时仅追加新数据,更新
items后保持滚动位置(RecycleScroller会自动复用已渲染项) - 避免同时触发刷新和加载:当
refreshing为 true 时,暂时忽略scroll触底判断
注意移动端特殊兼容点
部分机型或 WebView 下行为不一致,需针对性处理:
- iOS Safari 中,
scrollHeight可能滞后于实际内容高度,建议在数据更新后用nextTick再检查触底 - 安卓某些 WebView 对
touchmove.preventDefault()响应迟钝,可配合css touch-action: pan-y提升手势识别准确率 - 虚拟滚动组件若使用绝对定位,需确保父容器有明确高度(如
100vh减去 header/footer),否则无法计算可视区域

















