用 分段渲染 解决小程序长列表卡顿问题

在 H5 开发中,我们习惯通过监听 onScroll 事件,根据滚动位移实时计算并更新 DOM 节点。但在微信小程序中,由于逻辑层JS与视图层Webview的双线程架构,频繁滚动通信会导致 setData 积压,造成白屏和卡顿。

一、实现思路

利用原生提供的 IntersectionObserver 实现 分段渲染 。首先将成百上千条的扁平数据按固定数量(如10条一组)切分为多个块,在页面上对应渲染出一系列块容器锚点。不同于实时计算,我们仅监听这些块容器与视口的交叉状态,设置一个约 600px 的缓冲区,当块容器进入缓冲区时,利用 v-if 触发 DOM 的挂载。当其滑出缓冲区时,立即销毁内部节点以释放内存。为了防止滚动条在内容销毁后发生抖动或塌陷,动态记录每一个块在渲染时的真实高度并缓存到映射表中,内容卸载后通过 CSS 的 min-height 进行物理占位。


二、核心代码

1. 数据分块

小程序中 setData 的数据量直接影响渲染性能。通过 computed 进行分块,本质是将大数组渲染转化为分片渲染。

vue 复制代码
// 计算拿到分块数组
const chunkedList = computed(() => {
  const chunks = []
  for (let i = 0; i < list.length; i += chunkSize) {
    chunks.push({ id: i / chunkSize, items: list.slice(i, i + chunkSize) })
  }
  return chunks
})

2. 状态监听

传统的 onScroll 方案需要从视图层向逻辑层高频同步 scrollTop,造成双线程通信拥塞。而 IntersectionObserver 运行在原生渲染层,仅在达到触发条件时回调一次,极大地节省了 CPU 开销。

vue 复制代码
const visibleMap = ref({}) // 记录块的可视性
const heightMap = ref({})  // 记录块的高度
const instance = getCurrentInstance()
let observer = null

// 启动监测
const startObserver = () => {
  if (observer) observer.disconnect()
  // observeAll: true 允许同时监听所有符合条件的 .chunk-anchor
  observer = uni.createIntersectionObserver(instance.proxy, { observeAll: true })
  
  // 设置合适的缓冲区大小,让节点在进入视口前提前开始渲染
  observer.relativeToViewport({ top: 600, bottom: 600 })
    .observe('.chunk-anchor', (res) => {
      const { id } = res.dataset
      const isIntersecting = res.intersectionRatio > 0
      
      // 更新可见性
      visibleMap.value[id] = isIntersecting
      
      // 当块进入视口渲染完成后,立即捕捉真实高度并缓存
      if (isIntersecting && res.boundingClientRect.height > 0) {
        heightMap.value[id] = res.boundingClientRect.height
      }
    })
}

3. 模板占位

使用 min-height 解决长列表优化中的非固定高度导致的页面塌陷问题。

vue 复制代码
<template>
  <scroll-view class="scroll-container" scroll-y @scroll="$emit('scroll', $event)">
    <view 
      v-for="chunk in chunkedList" 
      :key="chunk.id"
      :data-id="chunk.id"
      class="chunk-anchor"
      :style="{ 
        /** 
         * 当块可见时,设为 auto 让内部元素撑开
         * 当块销毁时,使用 heightMap 记录的真实高度或 estimatedSize 初始预估高度进行物理占位
         */
        minHeight: visibleMap[chunk.id] ? 'auto' : (heightMap[chunk.id] || estimatedSize) + 'px' 
      }"
    >
      <template v-if="visibleMap[chunk.id]">
        <view v-for="item in chunk.items" :key="item[itemKey]">
          <slot name="list-item" :item="item" />
        </view>
      </template>
    </view>
  </scroll-view>
</template>

三、总结

显隐判断由小程序原生层处理,不依赖逻辑层的 onScroll 计算。滑出视口的块会被销毁,即使有数千个节点,页面始终只保持几十个真实 DOM 节点,极大降低内存占用。由于有缓冲区预渲染,用户几乎感知不到 DOM 的动态加载。通过 heightMap 自动记录块渲染后的真实高度,解决由于项高度不固定导致的滚动条跳动问题。

相关推荐
仨孩子的爹1 小时前
Vue实战|折腾两天,终于找到业务打印救星:vue-print-designer网页套打插件接入
前端
NGC_66112 小时前
tomcat相关知识点
前端·firefox
lecepin2 小时前
Agent Skills 完全科普指南
前端
三小河2 小时前
Vite 项目性能优化实践:从打包体积到首屏加载
前端
酉鬼女又兒2 小时前
HTML零基础快速入门篇(可用于备赛蓝桥杯Web应用开发) 牛客手把手戴刷FED1~8:基本标签,基本标签,媒体标签详解
前端·职场和发展·蓝桥杯·html·web
weixin199701080162 小时前
搜好货商品详情页前端性能优化实战
java·前端·python
SuperEugene2 小时前
NPM Script 实战:常用命令设计与封装|Vue 工程化篇
前端·javascript·vue.js·前端框架·npm
前端进阶之旅2 小时前
React 18 并发特性实战指南:提升大型应用性能的关键技术
前端·react.js·前端框架
恋猫de小郭2 小时前
Android 性能迎来提升:内核引入 AutoFDO 普惠所有 15-16 设备
android·前端·flutter