后端API一次返回10万条数据,前端如何处理
直接渲染 (不可取)
通过 setTimeout 进行分页渲染
-
每个页面有条数
limit
限制,数据可以分成Math.ceil(total/limit)
个页面,使用 setTimeout 顺序渲染页面,一次只渲染一个页面。 -
const renderList = async () => { const list = await getList() const total = list.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 = list[i] const div = document.createElement('div') div.className = 'addText' div.innerHTML = `<span>${item.text}</span>` container.appendChild(div) } render(page + 1) }, 0) } render(page) }
requestAnimationFrame
-
渲染页面的时候,我们可以使用requestAnimationFrame来代替setTimeout,这样可以减少reflow次数,提高性能
-
window.requestAnimationFrame() 方法告诉浏览器您希望执行动画,并请求浏览器调用指定函数在下一次重绘之前更新动画
-
同时可以先创建一个文档片段,在创建了 div 元素之后,再将元素插入到文档片段中。创建完所有 div 元素后,将片段插入页面,提高页面性能。
-
const renderList = async () => { const list = await getList() const total = list.length const page = 0 const limit = 200 const totalPage = Math.ceil(total / limit) const render = (page) => { if (page >= totalPage) return requestAnimationFrame(() => { const fragment = document.createDocumentFragment() for (let i = page * limit; i < page * limit + limit; i++) { const item = list[i] const div = document.createElement('div') div.className = 'addText' div.innerHTML = `<span>${item.text}</span>` fragment.appendChild(div) } container.appendChild(fragment) render(page + 1) }) } render(page) }
延迟加载
-
用户的屏幕只能同时显示有限的数据,所以我们可以采用延迟加载的策略,根据用户的滚动位置动态渲染数据
-
要获取用户的滚动位置,我们可以在列表末尾添加一个空节点空白。每当视口出现空白时,就意味着用户已经滚动到网页底部,这意味着我们需要继续渲染数据。
-
同时,我们可以使用getBoundingClientRect来判断空白是否在页面底部,getBoundingClientRect()返回值是一个 DOMRect 对象,包含整个元素的最小矩形(包括 padding 和 border-width)。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小
-
<script setup lang="ts"> import { onMounted, ref, computed } from 'vue' const getList = () => { return new Promise((resolve, reject) => { var ajax = new XMLHttpRequest(); ajax.open('get', 'http://127.0.0.1:8000'); ajax.send(); ajax.onreadystatechange = function () { if (ajax.readyState == 4 && ajax.status == 200) { resolve(JSON.parse(ajax.responseText)) } } }) } const container = ref<HTMLElement>() // container element const blank = ref<HTMLElement>() // blank element const list = ref<any>([]) const page = ref(1) const limit = 200 const maxPage = computed(() => Math.ceil(list.value.length / limit)) // List of real presentations 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) { // 当空白出现在页面底部,当前页数+1 page.value++ } } onMounted(async () => { const res = await getList() list.value = res }) </script> <template> <div id="container" @scroll="handleScroll" ref="container"> <div class="addText" v-for="(item) in showList" :key="item.tid"> <span>{{ item.text }}</span> </div> <div ref="blank"></div> </div> </template>