前端性能优化之虚拟列表

性能优化之虚拟列表

场景

尽管接口做了分页处理,但请求次数过多时,页面渲染的结构即长列表,会造成性能损耗。

描述

虚拟列表,就是让数据在固定个数的结构上渲染,结构不发生改变,而对应结构上的数据发生变化。

例如,设定列表容器容纳十个子组件,页面初始第一个子组件渲染的数据索引为0,当页面下滑一个子组件高度时,将第一个 子组件渲染的数据改为索引为1。

简而言之,获取的长列表数据可以十分大,几百、几千------

而虚拟列表只节选其中连续的十个,进行渲染展示。

技术背景

Vue3 Ts

页面结构

xml 复制代码
<div id="virtual-list--container" @scroll="vaScrollHandler" :style="{ 'height': listHeight }">
​
    <!-- 可视区 -->
    <div id="main-list--box" ref="visibleArea">
        <UserProfile :mode="0" v-for="(item, index) in fakeList" :key="index" :userInfo="item"></UserProfile>
    </div>
​
</div>
javascript 复制代码
/*
    在上述结构中,首先通过获取可视区的ref,获取它的容器高度,
    并除以虚拟列表总个数(10),得到每个子项的高度
*/
itemHeight.value = visibleArea.value?.clientHeight / 10
​
/*
    其中itemCount表示列表中显示出来的个数,
    比如8,表示列表总显示8个列表项,另2个总是由于滚动被遮住
*/
const listHeight = computed(() => itemCount * itemHeight.value + "px")     // 展示的列表高度

逻辑实现

在页面下滑过程中,如果距离底部还有一个列表项的高度,并且满足请求条件,则发起请求、添加数据。

接着,如果发生触底,那么就去节选所有数据allList中相应的十个,进行渲染fakeList

在页面下滑过程中,下滑了一个列表项的高度,其实就是虚拟列表结构整体渲染的数据的索引,都向后增加1位。

由于数据的更替,页面看起来就会闪烁,因此,此时需要做的就是通过scrollTop,纠正两个相同索引的数据,在前后两次页面渲染中的偏差。

对于向上滑动页面,同样的在触顶时更新渲染的数据fakeList,并修正偏差。

图示(来源掘金)

参数变量

scss 复制代码
const visibleArea = ref()
const itemHeight = ref(0)   // 单个item高度
const itemCount = 8    // 列表展示的个数8,而非当前页总数10
const listHeight = computed(() => itemCount * itemHeight.value + "px")     // 展示的列表高度
​
let startIndex = ref(0)
let endIndex = ref(9)
​
let isBottom = ref(false)           // 触底节流阀
let allowPreRequest = ref(true)    // 预加载阀
let initScrollTop = ref(0)
let hideCount = ref(0)          // 由于滚动隐藏的列表项个数
​
let allList = reactive<Object[]>([])    // 所有数据
let fakeList = reactive<Object[]>([])   // 虚拟列表渲染数据

滚动监听函数

ini 复制代码
const vaScrollHandler = (e: any) => {
    let { scrollTop, scrollHeight, clientHeight } = e.target
    
    /* 
        step4:触底或触顶时:更新触底变量,以使数据能刷新
        触底后修正了scrollTop,因此会进入这一层判断
    */
    if (scrollTop.toFixed(2) == itemHeight.value || scrollTop == 0) {
        isBottom.value = false
        allowPreRequest.value = true
    }
​
    // 判断滚动方向
    let direction = scrollTop - initScrollTop.value
    initScrollTop.value = scrollTop
    if (direction >= 0) {
        // 更新触底变量,否则每次只能触顶后才能触底更新
        isBottom.value = false
    } else {
        if (scrollTop == 0 && startIndex.value) {
            onReachTop()
        }
    }
​
    /* 
        step1:预加载下页数据(先)
        allowPreRequest节流阀用于由于scrollTop等参数的误差导致的重复进入判断
    */
    if (Math.ceil(scrollTop) + clientHeight >= Math.ceil(scrollHeight - itemHeight.value * 1) && allowPreRequest.value) {
        /*
            虚拟列表结束索引初始为9,每次触底都会更新endIndex的索引值(之后)
            如果结束索引endIndex 和 所有数据allList最后位元素的索引一致,满足更新条件,请求数据
        */
        if (endIndex.value == allList.length - 1) {
            mockRequest()
                .then((res: any) => {
                    res.forEach((item: any) => {
                        allList.push(item)
                    })
                    pageParam.pages++
                })
        }
        allowPreRequest.value = false
    }
​
​
    /*
        step2:触底判断(后)
        同样,isBottom节流阀用途一致
    */
    if (Math.ceil(scrollTop) + clientHeight == scrollHeight && !isBottom.value) {
        onReachBotoom()
        isBottom.value = true
    }
​
    /* 
        step3:触底后更新列表数据
    */
    function onReachBotoom() {
        hideCount.value = 10 - itemCount
​
        // 更新虚拟列表起始索引
        startIndex.value += hideCount.value - 1
        endIndex.value += hideCount.value - 1
​
​
        for (let i = 0; i < fakeList.length; i++) {
            /*
                这里是预防下滑速度过快,尽管成功发起了请求,
                但触底更新时数据仍不存在,导致页面列表项展示undefined
            */ 
            if (allList[i + startIndex.value]) fakeList[i] = allList[i + startIndex.value]
        }
​
        /*
            hideCount虚拟列表隐藏的个数是2,则前面更新索引时,其实虚拟列表渲染的数据只是整体向后移动了一位
            这里就是修正同一个数据的位移偏差,看起来不会抖
        */
        e.target.scrollTop = itemHeight.value
    }
​
    /* 
        上拉时更新列表数据
    */
    function onReachTop() {
        startIndex.value -= hideCount.value
        endIndex.value -= hideCount.value
​
        for (let i = 0; i < fakeList.length; i++) {
            fakeList[i] = allList[i + startIndex.value]
        }
​
        e.target.scrollTop = itemHeight.value
    }
​
}

效果展示

相关推荐
洒家肉山大魔王1 小时前
TPS和QPS的区别
性能优化·qps·tps
AntDreamer13 小时前
在实际开发中,如何根据项目需求调整 RecyclerView 的缓存策略?
android·java·缓存·面试·性能优化·kotlin
黑狼传说1 天前
前端项目优化:极致最优 vs 相对最优 —— 深入探索与实践
前端·性能优化
Lill_bin2 天前
Lua编程语言简介与应用
开发语言·数据库·缓存·设计模式·性能优化·lua
人工智能培训咨询叶梓3 天前
MobiLlama,面向资源受限设备的轻量级全透明GPT模型
人工智能·gpt·语言模型·自然语言处理·性能优化·多模态·轻量级
Flying_Fish_roe3 天前
JVM 性能优化与调优-ZGC(Z Garbage Collector)
jvm·性能优化
Flying_Fish_roe3 天前
JVM 性能优化与调优-GraalVM
jvm·性能优化
四代水门3 天前
游戏性能优化
游戏·性能优化
安卓美女4 天前
Android自定义View性能优化
android·性能优化
Flying_Fish_roe4 天前
JVM 性能优化与调优-Shenandoah GC
jvm·性能优化