长列表虚拟滚动

现在公司一直在做一个大模型AI问答的需求,那么问题来了,聊天记录可能有很多,几百条甚至上千条,如果直接渲染,对页面性能肯定是不友好的,可能会卡顿,所以就要考虑性能优化

调研了一下,发现长列表性能优化大概有三种办法:

  1. 分页,如果是基于antd组件库,那也不用赘述分页逻辑了,里面有一些分页组件,本篇文章不做过多解释
  2. 滚动加载(Infinite Scroll / 滚动加载更多)
  3. 虚拟滚动(Virtual Scroll)

下面详细介绍一下滚动加载和虚拟滚动

虚拟滚动

主要是为了解决"渲染性能"瓶颈,通过计算可视区域高度和每项高度,只渲染可视区域内的少量dom,其余用占位元素撑起滚动条,也就是说dom总量是固定的(可视区条数 + 上下缓冲),不随总数据量增长

优点:

  1. 内存占用比较低
  2. 不需要服务端配合做分页,适用于长列表、聊天消息、大数据表格

滚动加载

也是为了解决"数据量过大"问题,通过监听容器滚动事件,先加载少量数据,滚动到底部时再追加下一批数据,线性增长,滚得越多 DOM 越多,最终可能上万节点。

较虚拟加载:

  1. 内存占用较高,随数据量而增长,数据量极大时会卡顿,因为 DOM 不断累积
  2. 对服务器压力有点高,频繁分页请求,需要后端支持分页接口,适用于社交媒体 Feed、商品瀑布流、图片库、分页评论

总结:

  • 虚拟滚动 ="看多少,渲染多少",治标又治本,专注性能。
  • 滚动加载 ="用多少,加载多少 ",治标不治本,专注流量与体验。

虚拟滚动技术实现

我们项目用的vue3技术栈,可以用vue-virtual-scroller库,可以参考官网github.com/Akryum/vue-...

注意,如果是用的vue3 在安装依赖时,要用

js 复制代码
npm install vue-virtual-scroller@next  

官方的 vue-virtual-scroller 仓库里,Vue3 对应的版本 需要加 @next 标签,否则会默认拉取只兼容 Vue2 的旧版本,导致安装报错或运行时异常。

js 复制代码
// 如果每一项高度固定,用RecycleScroller组件,性能最好,内存占用率最低
import {RecycleScroller} from 'vue-virtual-scroller'
// 如果高度不固定(动态),比如聊天记录,DynamicScroller + DynamicScrollerItem 可以自动测量高度,稍慢但是灵活
import {DynamicScroller,DynamicScrollerItem} from 'vue-virtual-scroller'
// **记得引入 CSS** → 列表高度为 0,看不到滚动条
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

下面拿固高RecycleScroller组件举个例子,也是我做的一个demo,大家可以参考一下:

js 复制代码
<template>
  <div class="wrapper mx-auto">
    <div class="text-center text-lg">虚拟滚动(一万条数据)</div>
    <!-- 1 万条数据虚拟滚动 -->
    <RecycleScroller
      class="scroller"
      :items="items"
      :item-size="50"
      key-field="id"
      v-slot="{ item }"
    >
      <div class="item">
        <span>{{ item.id }}</span>
        {{ item.name }}
      </div>
    </RecycleScroller>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";

// 生成 1 万条假数据
const items = ref(
  Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    name: `用户 ${String(i).padStart(5, "0")}`,
  }))
);
</script>

<style lang="scss" scoped>
.wrapper {
  width: 500px;
  height: 600px;
  display: flex;
  flex-direction: column;
}

.scroller {
  flex: 1;
  border: 1px solid #e4e7ed;
}

.item {
  height: 50px;
  display: flex;
  align-items: center;
  padding: 0 16px;
  box-sizing: border-box;
  border-bottom: 1px solid #f5f5f5;
  font-size: 14px;

  span {
    width: 60px;
    color: #409eff;
    margin-right: 12px;
  }
}
</style>
相关推荐
阿眠4 分钟前
vue3实现web端和小程序端个人签名
前端·小程序·apache
哎呦薇16 分钟前
从开发到发布:手把手教你将Vue组件上传npm
前端·vue.js
Z7676_19 分钟前
静态路由技术
服务器·前端·javascript
慧一居士20 分钟前
npm 和 npx 区别对比
前端
用户38022585982423 分钟前
vue3源码解析:生命周期
前端·vue.js·源码阅读
遂心_23 分钟前
前端路由进化论:从传统页面到React Router的SPA革命
前端·javascript
前端菜鸟杂货铺29 分钟前
前端首屏优化及可实现方法
前端
遂心_29 分钟前
React Fragment与DocumentFragment:提升性能的双剑合璧
前端·javascript·react.js
ze_juejin30 分钟前
ionic、flutter、uniapp对比
前端
咚咚咚ddd30 分钟前
WebView Bridge 跨平台方案:统一 API 实现多端小程序通信
前端·前端工程化