今天面试的时候面试官直接问了一句后端一次性返回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.length
const page = 0
const limit = 200
const totalPage = Math.ceil(total / limit)
const render = (page) => {
if (page >= totalPage) return
setTimeout(() => {
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.length
const page = 0
const limit = 200
const totalPage = Math.ceil(total / limit)
const render = (page) => {
if (page >= totalPage) return
requestAnimationFrame(() => {
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>()
const blank = ref<HTMLElement>()
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) return
const clientHeight = container.value?.clientHeight
const blankTop = blank.value?.getBoundingClientRect().top
if (clientHeight === blankTop) {
page.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像素,注意要开定位和滚动条 -->
<div
class="virtualListWrap"
ref="virtualListWrap"
@scroll="handleScroll"
:style="{ height: itemHeight * count + 'px' }"
>
<div
class="placeholderDom"
:style="{ height: allListData.length * itemHeight + 'px' }"
></div>
<div class="contentList" :style="{ top: topVal }">
<div
v-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,
count: 10,
start: 0,
end: 10,
topVal: 0,
loading: false,
};
},
computed: {
showListData: 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>
如果你觉得此文对你有帮助的话,点个赞,鼓励一下作者。假如您也和我一样,在准备春招。欢迎加我微信shunwuyu
,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!”
作者-笨鸟更要先飞,欢迎关注!