现在公司一直在做一个大模型AI问答的需求,那么问题来了,聊天记录可能有很多,几百条甚至上千条,如果直接渲染,对页面性能肯定是不友好的,可能会卡顿,所以就要考虑性能优化
调研了一下,发现长列表性能优化大概有三种办法:
- 分页,如果是基于antd组件库,那也不用赘述分页逻辑了,里面有一些分页组件,本篇文章不做过多解释
- 滚动加载(Infinite Scroll / 滚动加载更多)
- 虚拟滚动(Virtual Scroll)
下面详细介绍一下滚动加载和虚拟滚动
虚拟滚动
主要是为了解决"渲染性能"瓶颈,通过计算可视区域高度和每项高度,只渲染可视区域内的少量dom,其余用占位元素撑起滚动条,也就是说dom总量是固定的(可视区条数 + 上下缓冲),不随总数据量增长
优点:
- 内存占用比较低
- 不需要服务端配合做分页,适用于长列表、聊天消息、大数据表格
滚动加载
也是为了解决"数据量过大"问题,通过监听容器滚动事件,先加载少量数据,滚动到底部时再追加下一批数据,线性增长,滚得越多 DOM 越多,最终可能上万节点。
较虚拟加载:
- 内存占用较高,随数据量而增长,数据量极大时会卡顿,因为 DOM 不断累积
- 对服务器压力有点高,频繁分页请求,需要后端支持分页接口,适用于社交媒体 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>
