前言
今天面试的时候面试官直接问了一句后端一次性返回10万条
数据给你,你如何处理?,我脑中浮现的第一句话就是拿着物理学圣剑找后端进行 “友好的协商”,谁打赢了听谁的。不过虽然这种情况很少,不过我在实际开发中还真遇到了类似的情况,接下来我将带着大家一些处理方案。
正文
方案一 直接渲染
如果请求到10万条数据直接渲染,页面会卡死的,很显然,这种方式是不可取的。 pass!
async getData() {this.loading = true;const res = await axios.get("/api/getData");this.arr = res.data.data;this.loading = false;
}
方案二 setTimeout分页渲染
这个方法就是,把10w
按照每页数量limit
分成总共Math.ceil(total / limit)
页,然后利用setTimeout
,每次渲染1页数据,这样的话,渲染出首页数据的时间大大缩减了。
const renderData = async () => {const Data = await getData()const total = Data.lengthconst page = 0//每页数量const limit = 200//总页数const totalPage = Math.ceil(total / limit)const render = (page) => {if (page >= totalPage) returnsetTimeout(() => {for (let i = page * limit; i < page * limit + limit; i++) {const item = Data[i]const div = document.createElement('div')div.className = 'sunshine'div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`container.appendChild(div)}render(page + 1)}, 0)}render(page)
}
方案三 requestAnimationFrame
使用requestAnimationFrame
代替setTimeout
,减少了重排
的次数,极大提高了性能,建议大家在渲染方面多使用requestAnimationFrame
。
const renderData = async () => {const Data = await getData()const total = Data.lengthconst page = 0//每页数量const limit = 200//总页数const totalPage = Math.ceil(total / limit)const render = (page) => {if (page >= totalPage) return// 使用requestAnimationFrame代替setTimeoutrequestAnimationFrame(() => {for (let i = page * limit; i < page * limit + limit; i++) {const item = Data[i]const div = document.createElement('div')div.className = 'sunshine'div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`container.appendChild(div)}render(page + 1)})}render(page)
}
方案四 表格滚动触底加载
原理很简单,就是在列表尾部放一个空节点,然后先渲染第1页数据,向上滚动,等到空节点出现在视图中,就说明到底了,这时候再加载第二页,往后以此类推。
至于怎么判断blank
出现在视图上,可以使用getBoundingClientRect
方法获取top
属性。也可以用 js 的IntersectionObserver
API 来实现
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'const container = ref<HTMLElement>() // container节点
const blank = ref<HTMLElement>() // blank节点
const list = ref<any>([]) // 列表
const page = ref(1) // 当前页数
const limit = 200 // 一页展示
// 最大页数
const maxPage = computed(() => Math.ceil(list.value.length / limit))
// 真实展示的列表
const showList = computed(() => list.value.slice(0, page.value * limit))
const handleScroll = () => {// 当前页数与最大页数的比较if (page.value > maxPage.value) returnconst clientHeight = container.value?.clientHeightconst blankTop = blank.value?.getBoundingClientRect().topif (clientHeight === blankTop) {// 出现在视图,则当前页数加1page.value++}
}onMounted(async () => {const res = await getList()list.value = res
})
</script><template><div id="container" @scroll="handleScroll" ref="container"><div class="sunshine" v-for="(item) in showList" :key="item.tid"><img :src="item.src" /><span>{{ item.text }}</span></div><div ref="blank"></div></div>
</template>
方案五 虚拟列表
什么是虚拟列表?
所谓的虚拟列表实际上是前端障眼法的一种表现形式。
看到的好像所有的数据都渲染了,实际上只渲染可视区域的部分罢了。如果10万条数据都渲染,那得需要多少dom节点元素呢?所以我们只给用户看,他当下能看到的如果用户要下拉滚动条或者上拉滚动条再把对应的内容呈现在可视区域内。这样就实现了看着像是所有的dom元素每一条数据都有渲染的障眼法效果了
实现
<template><!-- 虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据比如窗口只能看到10条数据,一条40像素,10条400像素故,窗口的高度为400像素,注意要开定位和滚动条 --><divclass="virtualListWrap"ref="virtualListWrap"@scroll="handleScroll":style="{ height: itemHeight * count + 'px' }"><!-- 占位dom元素,其高度为所有的数据的总高度 --><divclass="placeholderDom":style="{ height: allListData.length * itemHeight + 'px' }"></div><!-- 内容区,展示10条数据,注意其定位的top值是变化的 --><div class="contentList" :style="{ top: topVal }"><!-- 每一条(项)数据 --><divv-for="(item, index) in showListData":key="index"class="itemClass":style="{ height: itemHeight + 'px' }">{{ item.name }}</div></div><!-- 加载中部分 --><div class="loadingBox" v-show="loading"><i class="el-icon-loading"></i> <span>loading...</span></div></div>
</template>
<script>
import axios from "axios";
export default {data() {return {allListData: [], // 所有的数据,比如这个数组存放了十万条数据itemHeight: 40, // 每一条(项)的高度,比如40像素count: 10, // 一屏展示几条数据start: 0, // 开始位置的索引end: 10, // 结束位置的索引topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动loading: false,};},computed: {// 从所有的数据allListData中截取需要展示的数据showListDatashowListData: function () {return this.allListData.slice(this.start, this.end);},},async created() {this.loading = true;const res = await axios.get("/api/getData");this.allListData = res.data.data;this.loading = false;},methods: {// 滚动这里可以加上节流,减少触发频次handleScroll() {const scrollTop = this.$refs.virtualListWrap.scrollTop;this.start = Math.floor(scrollTop / this.itemHeight);this.end = this.start + this.count;this.topVal = this.$refs.virtualListWrap.scrollTop + "px";},},
};
</script>
结尾
如果你觉得此文对你有帮助的话,点个赞,鼓励一下作者。