vue 长列表性能优化

455 阅读1分钟

列表数据过多会存在什么问题

  • 数据过多渲染时间很长
  • dom节点过多造成页面卡顿

解决方案

1 分片加载

分片加载的原理 将一次js执行时间+ 一次渲染时间,优化成 n 次 js执行间, 存在 n 次渲染

    <div id="container"></div>
    var container = document.querySelector('#container')
    // 记录加载位置
    var index = 0
    // 每次加载1000 条数据 
    function loadData() {
        // 限制加载总数
        if(index>= 500000){
            return
        }
        // 渲染数据
        for(var i=1; i<= 1000, i++) {
            var div = document.createElement('div')
            div.innnerText = index + i
        }
        // 数据变化
        index+= 1000
        // 加载下个分片
        // loadData() 瞬间加载,错误写法
        setTimeout(loadData(),0) // 在下一次宏任务中进行加载,正确写法
    }

为什么不能直接调用loadData方法 直接加载,而要使用setTimeout来加载数据?

因为js 是单线程的,主线程只有一个(能操作DOM的),线程有 js引擎线程:执行js代码、GUI 渲染线程:渲染页面,事件、定时器、Ajax 是单独线程,最后还有Eventloop线程。 当一轮eventloop结束后 js引擎线程代码执行完毕以后就去渲染页面(执行GUI渲染线程),在渲染完毕后,从宏任务队列中取出第一个任务进行执行。所以我们不能直接调用loadData方法,而是要重新开一个宏任务。

2 虚拟列表 虚拟列表加载原理,只对屏幕可视区域进行渲染,对不可见区域的数据不渲染或者部分渲染。

image.png

<template>
    <div class="container scroll-touch" ref="container" :style="{height:containerHeight + 'px'}" @scroll="handleScroll">
        <div class="list" :style="{height:totalHeight, paddingTop:top}">
            <div class="list-item"
                :style="{height:itemHeight + 'px'}"
                v-for="item in shownData" :key="item.id">
                {{item.content}}
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        name:'VirtualScrollList',
        data() {
            return {
                start:0, //显示数组的开始下标
                end: this.pageSize * 3, // 显示数组的结束下标
            }
        },
        props:{
            // 一页显示多少条数据
            pageSize:{
                type:Number,
                default:10
            },
            // 所有列表数据
            items: {
                type:Array,
                required:true
            },
            // 一列列表的高度
            itemHeight: {
                type:Number,
                required:true
            }
        },
        computed:{
            shownData() {
                return this.items.slice(this.start,this.end)
            },
            totalHeight() {
                return this.items.length * this.itemHeight + 'px'
            },
            containerHeight() {
                return this.itemHeight * this.pageSize 
            },
            top() {
                return this.start * itemHeight + 'px'
            }
        },
        methods: {
            handleScroll() {
                const scrollTop = this.$refs.container.scrollTop
                const currentPageSize = Math.floor(scrollTop / this.containerHeight)
                //当滑动到倒数1、2、3页面的时候不去改变数组下标
                 if((this.pageSize * currentPageSize+ (this.pageSize * 3)<= this.items.length)) {
                    this.start = this.pageSize * currentPageSize
                    this.end = this.start + (this.pageSize * 3)
                }
                
            }
        }
    }
</script>
<style scoped>
    .container {
        overflow-y:auto;
        width:500px;
        margin:auto;
        background:#f1f1f1;
    }
    .list {
        width:100%;
        box-sizing: border-box;
    }
    .list-item {
        display: flex;
        justify-content: center;
        align-items: center;
        border-bottom:1px solid #d3d3d3;
        box-sizing:border-box;
    }
    .scroll-touch {
        -webkit-overflow-scrolling: touch;
    }
   .scroll-touch::-webkit-scrollbar {
         width: 10px;
    }
   .scroll-touch::-webkit-scrollbar-thumb {
       border-radius: 0;
       background: rgba(0,0,0,.6);
   }
  .scroll-touch::-webkit-scrollbar-track {
      background: #f4f4f4;
   }
</style>

至此我们已经完成一个虚拟列表,下面让我们看看效果

VeryCapture_20221209151836.gif