前端如何更好的展⽰后端返回的⼗万条数据⽬录
前置⼯作
后端搭建
前端页⾯
直接渲染
setTimeout分页渲染
requestAnimationFrame
⽂档碎⽚ + requestAnimationFrame
懒加载
今天跟⼤家来唠唠嗑,如果后端真的返回给前端10万条数据,咱们前端要怎么优雅地展⽰出来呢?
前置⼯作
先把前置⼯作给做好,后⾯才能进⾏测试
后端搭建
新建⼀个 server.js ⽂件,简单起个服务,并返回给前端 10w 条数据,并通过 nodemon server.js 开启服务
没有安装 nodemon 的同学可以先全局安装 npm i nodemon -g
// server.js
const http = require('http')
const port = 8000;
// 开启Cors
res.writeHead(200, {
//设置允许跨域的域名,也可设置*允许所有域名
'Access-Control-Allow-Origin': '*',
//跨域允许的请求⽅法,也可设置*允许所有⽅法
"Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
//允许的header类型
'Access-Control-Allow-Headers': 'Content-Type'
})
let list = []
let num = 0
// ⽣成10万条数据的list
for (let i = 0; i < 1000000; i++) {
num++
list.push({
src: 'p3-passport.byteacctimg/img/user-avatar/d71c38d1682c543b33f8d716b3b734ca~300x300.image',
text: `我是${num}号嘉宾林三⼼`,
tid: num
})
}
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
前端页⾯
先新建⼀个 index.html
// index.html
// 样式
发送ajax请求的步骤<style>
* {
padding: 0;
margin: 0;
}
#container {
height: 100vh;
overflow: auto;
}
.sunshine {
display: flex;
padding: 10px;
}
img {
width: 150px;
height: 150px;
}
</style>
// html部分
<body>
<div id="container">
</div>
<script src="./index.js"></script>
</body>
然后新建⼀个 index.js ⽂件,封装⼀个 AJAX 函数,⽤来请求这 10w 条数据
// index.js
// 请求函数
const getList = () => {
return new Promise((resolve, reject) => {
//步骤⼀:创建异步对象
var ajax = new XMLHttpRequest();
//步骤⼆:设置请求的url参数,参数⼀是请求的类型,参数⼆是请求的url,可以带参数
ajax.open('get', '127.0.0.1:8000');
//步骤三:发送请求
ajax.send();
//步骤四:注册事件 onreadystatechange 状态改变就会调⽤
if (adyState == 4 && ajax.status == 200) {
//步骤五如果能够进到这个判断说明数据完美的回来了,并且请求的页⾯是存在的
resolve(JSON.sponseText))
}
}
})
}
// 获取container对象
const container = ElementById('container')
直接渲染
最直接的⽅式就是直接渲染出来,但是这样的做法肯定是不可取的,因为⼀次性渲染出 10w 个节点,是⾮常耗时间的,咱们可以来看⼀下耗时,差不多要消耗 12秒,⾮常消耗时间
const renderList = async () => {
console.time('列表时间')
const list = await getList()
list.forEach(item => {
const div = ateElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${}</span>`
container.appendChild(div)
})
console.timeEnd('列表时间')
}
renderList()
setTimeout分页渲染
这个⽅法就是,把 10w 按照每页数量 limit 分成总共 il(total / limit) 页,然后利⽤ setTimeout ,每次渲染1页数据,这样的话,渲染出⾸页数据的时间⼤⼤缩减了
const renderList = async () => {
console.time('列表时间')
const list = await getList()
console.log(list)
const total = list.length
const page = 0
const limit = 200
const totalPage = il(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 = ateElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${}</span>`
container.appendChild(div)
}
render(page + 1)
}, 0)
}
render(page)
console.timeEnd('列表时间')
}
requestAnimationFrame
使⽤ requestAnimationFrame 代替 setTimeout ,减少了重排的次数,极⼤提⾼了性能,建议⼤家在渲染⽅⾯多使
⽤ requestAnimationFrame
const renderList = async () => {
console.time('列表时间')
const list = await getList()
console.log(list)
const total = list.length
const page = 0
const limit = 200
const totalPage = il(total / limit)
const render = (page) => {
if (page >= totalPage) return
// 使⽤requestAnimationFrame代替setTimeout
requestAnimationFrame(() => {
for (let i = page * limit; i < page * limit + limit; i++) {
const item = list[i]
const div = ateElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${}</span>`
container.appendChild(div)
}
render(page + 1)
}, 0)
}
render(page)
console.timeEnd('列表时间')
}
⽂档碎⽚ + requestAnimationFrame
⽂档碎⽚的好处
1、之前都是每次创建⼀个 div 标签就 appendChild ⼀次,但是有了⽂档碎⽚可以先把1页的 div 标签先放进⽂档碎⽚中,然后⼀次性 appendChild 到 container 中,这样减少了 appendChild 的次数,极⼤提⾼了性能
2、页⾯只会渲染⽂档碎⽚包裹着的元素,⽽不会渲染⽂档碎⽚
const renderList = async () => {
console.time('列表时间')
const list = await getList()
console.log(list)
const total = list.length
const page = 0
const limit = 200
const totalPage = il(total / limit)
const render = (page) => {
if (page >= totalPage) return
requestAnimationFrame(() => {
// 创建⼀个⽂档碎⽚
const fragment = ateDocumentFragment()
for (let i = page * limit; i < page * limit + limit; i++) {
const item = list[i]
const div = ateElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${}</span>`
// 先塞进⽂档碎⽚
fragment.appendChild(div)
}
/
/ ⼀次性appendChild
container.appendChild(fragment)
render(page + 1)
}, 0)
}
render(page)
console.timeEnd('列表时间')
}
懒加载
为了⽐较通俗的讲解,咱们启动⼀个 vue 前端项⽬,后端服务还是开着
其实实现原理很简单,咱们通过⼀张图来展⽰,就是在列表尾部放⼀个空节点 blank ,然后先渲染第1页数据,向上滚动,等到 blank 出现在视图中,就说明到底了,这时候再加载第⼆页,往后以此类推。
⾄于怎么判断 blank 出现在视图上,可以使⽤ getBoundingClientRect ⽅法获取 top 属性
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
const getList = () => {
// 跟上⾯⼀样的代码
}
const container = ref<HTMLElement>() // container节点
const blank = ref<HTMLElement>() // blank节点
const list = ref<any>([]) // 列表
const page = ref(1) // 当前页数
const limit = 200 // ⼀页展⽰
/
/ 最⼤页数
const maxPage = computed(() => il(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) {
// blank出现在视图,则当前页数加1
page.value++
}
}
onMounted(async () => {
const res = await getList()
list.value = res
})
</script>
<template>
<div class="container" @scroll="handleScroll" ref="container">
<div class="sunshine" v-for="(item) in showList" :key="item.tid">
<img :src="item.src" />
<span>{{ }}</span>
</div>
<div ref="blank"></div>
</div>
</template>
以上就是前端如何更好的展⽰后端返回的⼗万条数据的详细内容,更多关于前端展⽰后端返回的⼗万条数据的资料请关注其它相关⽂章!