likes
comments
collection
share

万条数据如何加载显示(长列表优化)

作者站长头像
站长
· 阅读数 18

当后端返回一万条数据,前端如何进行处理?

万条数据如何加载显示(长列表优化)

最开始仅仅是把他当作幽默的段子,实际上在真实的业务需求中,会存在后端返回大量数据,这时就得思考解决方法,最好是合理分页或者使用滚动加载等技术来优化用户体验和性能。

接下来总结才学会的通过虚拟列表实现长列表加载功能。主要实现思路如下:

写一个代表可视区域的div固定高度。通过overflow使其允许纵向y轴滚动。计算可视区域中可以显示的数据条数。用可视区域高度除以单条数据高度就可以得到。监听滚动,当滚动条滚动的时候,计算出被卷起的数据的高度。计算可视区域内数据的起始索引,也就是区域内的第一条数据,用卷起的高度除以单条数据的高度。计算可视区域内数据的结束索引。通过起始索引加上刚刚计算出来的可以显示的数据的条数。取起始索引和结束索引中间的数据渲染到可视化区域。计算起始索引对应的数据在列表中的偏移位置,并设置到列表上。

未做优化

未优化直接循环10000条数据渲染效果,通过F12性能(Performance),录制div加载渲染的时间: 万条数据如何加载显示(长列表优化)

实现的代码包含两个文件一个index.vueItemList.vue组件两个文件,可以看出很简单通过v-if进行10000条数据循环并创建真实的dom,这样会耗费大量的时间绘制渲染dom,我最开始运行下面的代码,电脑风扇,并且js运行阻塞给人一种页面卡死的感觉,滑动列表也会卡顿(重新渲染)。

下面是index.vue代码:

<template>
    <div class="my-long-list">
        <div id="list" v-for="item in items" :key="item.id">
            <ItemList :item="item"></ItemList>
        </div>
    </div>
 
</template>

<script>
import ItemList from './ItemList.vue';
//获取一个10000的数组
var items = [] ;

for (let i = 0; i < 10000; i++) {
            items.push({
                id: i,
                name: 'item' + i,
                age: i,
                address: 'address' + i
            })
        }
export default {
    data() {
        return {
            items//将10000数组添加到data中
        }
    },
    // 初始化
    mounted() {

    },
    components: {
        ItemList
    }
}
</script>

<style>
.my-long-list{
    width: 500px;
    height: 500px;
    overflow: auto;
    border: 1px solid red;
}
</style>

下面是ItemList.vue组件的代码:

<template>
    <div class="item-list">
        <div >{{ item.name }}</div>
        <div>年龄:{{ item.age }}</div>
        <div>地址:{{ item.address }}</div>
    </div>
</template>

<script>
export default {
    name: 'ItemList',
    props: {
        item: {
            type: Array,
            default: () => []
        }
    },
}
</script>

<style>
.item-list{
    width: 400px;
    height: 50px;
    border: 1px solid #000;
    background-color: #fff;
    margin: 0 auto;
    text-align: center;
    display: flex;
    align-items: center;
    
justify-content: space-between;
    overflow: hidden;
}
.item-list div{
    margin: 5px;
}
</style>

分析需求

我们先理解页面显示列表的原理,其实不管你加载多少条数据页面只会展示,看见区域的内容,而看到见的区域是有限的,例如我上面的案例,最外层div height:500px,每个ItemList高度50px,那么最多可以展示11个ItemList(最上面展示一半,最下面展示一半),如果我们仅仅加载可以看见的内容那效率是不是就能提升很多。 万条数据如何加载显示(长列表优化) 找到方向那就大胆假设,小心求证,找好切入的点,把大问题简化,先拆分三个组件,index.vue一个生成10000条数据,将数据传入到组件scroller.vue中进行处理。在scroller.vue生成一个可以滚动看空白列表,再根据滚动的高度计算需要显示的items数组将显示的数组截取处理进行显示,而不再显示中的则不获取。ItemList.vue显示需要展示区域。

万条数据如何加载显示(长列表优化)

优化代码

index.vue页面代码如下,引入两个组件ItemListScroller,删除了overflow: auto; 将上下滚动功能交给Scroller进行处理。

<template>
    <div class="my-long-list">
        <!-- 创建一个没有内容的容器,目的让滚动条在滚动的时候显示  ,-->
         <!-- v-slot 使用插槽,将ItemList组件传入到Scroller组件中-->
        <Scroller 
        v-slot="{ item }"
        :items="items" >
            <ItemList :item="item"></ItemList>
        </Scroller>
    </div>
</template>

<script>
import Scroller from './Scroller.vue';
import ItemList from './ItemList.vue';

var items = [];

for (let i = 0; i < 10000; i++) {
    items.push({
        id: i,
        name: 'item' + i,
        age: i,
        address: 'address' + i
    })
}
export default {
    data() {
        return {
            items //需要展示的数据
        }
    },
    computed: {
    },
    components: {
        ItemList,
        Scroller
    }
}
</script>

<style>
.my-long-list {
    width: 500px;
    height: 500px;
    margin: 0 auto;
    border: 1px solid red;
}
</style>

Scroller.vue代码如下:

<template>
    <!-- 创建一个容器,在最后设置滚动效果 -->
    <div class="my-scroller-container" @scroll="init" ref="container">
        
    <!-- 创建一个没有内容的容器,目的让滚动条在滚动的时候显示 -->
        <div class="my-scroller-wrapper" :style="{ height: totalSize + 'px' }">
            <!-- 创建一个实际显示区域的容器,循环每条数据 -->
            <div class="my-scroller-item" 
            v-for="poolItem in datalist" 
            :key="poolItem.item.id" :style="{
                transform: `translateY(${poolItem.position}px)`,
            }">
                <slot :item="poolItem.item"></slot>
            </div>
        </div>

    </div>
</template>

<script>
export default {
    props: {
        items: {
            type: Array,
            default: () => []
        }
    },
    data() {
        return{
            // 初始化数据
            datalist:[]
        }
    },
    computed: {
        //根据总条数,计算高度
        totalSize() {
            return this.items.length * 50;
        },
    },
    // 初始化
    mounted() {
        // window.vm = this;
        this.init();
    },
    methods:{
         /**
         * 初始化
         * 重新处理每条数据的位置 和 内容
         * @param {*}
         */
         init() {
            let {minSize,maxSize} = this.setShowNum();
            // 重新处理每条数据的位置 和 内容
            this.datalist = this.items.slice(minSize,maxSize).map((item, index) => {
                return {
                    item,
                    position: minSize*50 + index * 50
                }
            })
            window.vm = this;
            // console.log("items",this.datalist);
        },
        // 根据容器高度计算可显示区域条数
        setShowNum() {
            const scrollTop = this.$refs.container.scrollTop;
            const height = this.$refs.container.clientHeight;
            let minSize = Math.floor(scrollTop/50) ;
            let maxSize = Math.ceil((scrollTop+height)/50);
            // console.log("移动位置和显示容器高度",scrollTop,height,minSize,maxSize);
            return {minSize,maxSize}
        }
        
    }
}
</script>

<style>
.my-scroller-container {
    width: 500px;
    height: 500px;
    overflow: auto;
}
.my-scroller-wrapper {
    position:relative;
}

.my-scroller-item {
    position: absolute;
    width: 100%;
    left: 0;
    top: 0;
}
</style>

ItemList.vue组件代码参考上面修改前(没有改动)。

功能解析

下面详细讲讲实现思路和逻辑, 第一步:首先如上面的代码创建了两个divmy-scroller-containermy-scroller-wrapper并给他们添加样式:my-scroller-container是最终显示的div,添加overflow: auto; 让他内部可以滚动。my-scroller-wrapper是一个空白的div,高度通过计算属性总条数*每条高度计算得到:style="{ height: totalSize + 'px' }" 万条数据如何加载显示(长列表优化)

第二步:通过使用Vue的Refs机制,获取my-scroller-container元素的滚动条位置,并将其存储在变量scrollTop中。获取my-scroller-container元素高度height 有这两个值可以计算出中间区域需要显示几个ItemList

万条数据如何加载显示(长列表优化)

万条数据如何加载显示(长列表优化)

        // 根据容器高度计算可显示区域条数
        setShowNum() {
            const scrollTop = this.$refs.container.scrollTop;
            const height = this.$refs.container.clientHeight;
            let minSize = Math.floor(scrollTop/50) ;
            let maxSize = Math.ceil((scrollTop+height)/50);
            // console.log("移动位置和显示容器高度",scrollTop,height,minSize,maxSize);
            return {minSize,maxSize}
        }

通过setShowNum可以计算出当前位置需要渲染的items最小和最大(下标),通过init函数计算得到需要渲染的数组,并讲itemList绝对布局,根据数组下标通过计算偏移量position,然后修改transform: translateY(${poolItem.position}px),实现每个itemLis都展示在对应的位置,而隐藏区域则不渲染,最后再添加滚动监听,每次滚动触发@scroll="init",发生改变调用init()函数,重新计算出ItemList位置。

万条数据如何加载显示(长列表优化)

         /**
         * 初始化
         * 重新处理每条数据的位置 和 内容
         * @param {*}
         */
         init() {
            let {minSize,maxSize} = this.setShowNum();
            // 重新处理每条数据的位置 和 内容
            this.datalist = this.items.slice(minSize,maxSize).map((item, index) => {
                return {
                    item,
                    position: minSize*50 + index * 50
                }
            })
            window.vm = this;
            // console.log("items",this.datalist);
        },

调试技巧

调试,将当前组件绑定到window对象中,比如上面的代码中window.vm = this;,绑定之后可以再控制台直接获取vue组件数据,方便调试。

 // 初始化
    mounted() {
        // window.vm = this;
        this.init();
    },

万条数据如何加载显示(长列表优化)

查看渲染性能

万条数据如何加载显示(长列表优化)

可以看出渲染响应远远快于未优化前,这种是用空间换时间,虽然每次滑动都会重新计算一次,由于每次只会计算10多条性能压力可以忽悠不计,由于渲染时间短,给用户感受更好,不会卡顿死机。

扩展

最后提第三方组件vue-virtual-scoller,使用它可以快速方便的的实现上面的功能: github.com/Akryum/vue-…