手搓 uniapp vue3 虚拟列表遇到的坑

虚拟列表的核心思想

虚拟列表的核心其实很简单:只渲染用户能看到的那部分内容。当用户滚动时,动态计算哪些元素应该出现在可视区域内,然后只渲染这些元素,在大数据量列表中能显著提升性能。

基本实现思路

  1. 用一个固定高度的容器包裹整个列表
  2. 计算总内容高度(用占位元素撑开)
  3. 监听滚动事件,计算当前可视区域的起始索引
  4. 只渲染可视区域内的元素,其他用空白填充
  5. 通过 transform 来定位实际内容

遇到的坑

1. 占位符导致的布局问题

最开始实现时,占位符和列表容器都在文档流内,导致列表容器相对占位符定位而偏离了位置,解决办法就是让占位符脱离文档流,设为绝对定位,外层容器保持相对定位。

2. 滚动闪烁问题

在快速滚动时,有时会出现空白闪烁。原因是:

  • 预渲染的缓冲元素数量不足
  • 元素尺寸测量延迟

通过增加 buffer属性和异步测量解决了这个问题:

arduino 复制代码
const visibleRange = computed(() => {
  // ... 计算逻辑
  return { start, end: Math.min(end + props.buffer, props.list.length) }
})

buffer其实就是预加载的缓冲区域,设置为3则代表前后多渲染3个item,这样就可以防止滑动过快导致渲染跟不上而白屏。

3. 元素尺寸测量不准

在 UniApp 中测量元素尺寸需要使用 uni.createSelectorQuery(),但这里有几个问题:

  • 必须在组件挂载后才能测量
  • 异步测量导致初始渲染时使用预估尺寸
  • 元素动态变化时尺寸需要重新测量

解决方案:

typescript 复制代码
const measureItem = (index: number) => {
  const itemIndex = visibleRange.value.start + index
  if (measured.has(itemIndex)) return
  
  uni
    .createSelectorQuery()
    .in(instance?.proxy)
    .select(`.item-${itemIndex}`)
    .boundingClientRect((res: any) => {
      if (res) {
        const size = isVertical.value ? res.height : res.width
        itemSizes.value[itemIndex] = size
        measured.add(itemIndex)
      }
    })
    .exec()
}

用 measuredSet 记录已经测量过的元素,避免重复测量。

4. 滚动性能优化

直接在 @scroll事件中更新状态在快速滚动时会有性能问题,但 UniApp 的 scroll-view本身就有节流,所以没有额外处理。如果需要更精细的控制,可以考虑用防抖或节流包装滚动处理函数。

5. 数据更新处理

当列表数据变化时,之前测量的尺寸就失效了。我通过 watch 监听数据变化来重置:

scss 复制代码
watch(
  () => props.list,
  () => {
    itemSizes.value = []
    measured.clear()
  },
  { deep: true }
)

6. 水平滚动的适配

为了让组件同时支持垂直和水平滚动,需要加一点条件判断:

javascript 复制代码
const isVertical = computed(() => {
  return props.direction === 'vertical'
})

// 在样式中
transform: isVertical.value
  ? `translateY(${startOffset.value}px)`
  : `translateX(${startOffset.value}px)`

使用示例

ini 复制代码
<yt-virtual-list
  :list="data"
  direction="vertical"
  height="200px"
  :buffer="3"
  :estimatedSize="40"
>
  <template #list-item="{ item, index }">
    <view class="list-item">
      <text>{{ index }}. {{ item.name }}</text>
    </view>
  </template>
</yt-virtual-list>

总结

手搓虚拟列表最大的收获是理解了虚拟滚动的原理。虽然有一些坑,但解决了之后的效果还是很明显的,特别是在移动端处理长列表时,性能提升非常显著。 关键点:

  • 正确计算和更新元素位置
  • 合理的缓冲机制防止空白
  • 准确的尺寸测量
  • 处理好数据变化时的状态重置

虚拟列表在移动端开发中是个很实用的优化手段,值得花时间理解和实现。

相关推荐
szarron13 分钟前
VNA6 便携式矢量网络分析仪|1MHz‑6.3GHz 手持 VNA 真实应用场景全解析
前端·嵌入式硬件·信息可视化·数据挖掘·数据分析
Reisentyan15 分钟前
B站首页,点击视频卡片之后首页自动刷新的问题
前端
默_笙23 分钟前
🚕 缺料就出门买:给 RAG 装上"信息够不够"的判断力
前端·javascript
Liora_Yvonne39 分钟前
同样用 Element Plus,为什么你的后台总有一股“模板味”?
前端
葡萄城技术团队44 分钟前
用 Sheet 页导航视图管理复杂 SpreadJS 工作簿
前端
志尊宝1 小时前
Vue3 零基础每日笔记(054):Pinia 三件套详解——state / getters / actions 全搞懂
前端·javascript·vue.js·vue·前端开发
Caroline5161 小时前
GPT Image 2.5还能这么玩!一套提示词生成IP吉祥物,附完整案例
前端·gpt·tcp/ip
wangyadong3171 小时前
# el-table 复选框无法选中问题记录
前端·javascript·vue.js
葡萄城技术团队1 小时前
给 SpreadJS 单元格装上自动完成
前端
恋猫de小郭2 小时前
Flutter 多窗口重要优化合并,多窗口性能和实用性大幅提升
android·前端·flutter