相信有一道耳熟能详的题目,如果前端获取到了 10w 条数据,应该怎么渲染?本文就以此为例,来进行切入,解析大量数据渲染的方案
样式代码比较简单,我就不做阐述了,展示一下直接渲染的js代码,如下:
const data = []
for (let i = 0; i < 100000; i++) {
data.push({ id: i, name: `Item ${i}` })
}
const box = document.querySelector('.box')
const btn = document.querySelector('.btn')
btn.addEventListener('click', function () {
render(data)
})
function render(list) {
const htmlStr = list
.map(item => {
return `<div class="item">
<span>${item.id}</span>
<span>${item.name}</span>
</div>`
})
.join('')
box.innerHTML = htmlStr
}
看一下结果:
从点击渲染开始,经过了一段较长的时间才渲染出来了dom,这还只是一些简单的dom结构,如果是一些复杂的dom的结构,那相信时间会更加的漫长,而实际上我们通常是不需要一次性直接看到全部的数据的,只需要满足最开始展示在容器范围内的数据即可,或者适当多出一些,所以我们不难想到,只要我分开渲染,每次渲染一部分,虽然总时间变长,但是从体验上来说,会好上很多
现在我们拿到的数据是一个10w个数据的一维数组,但是如果我们需要每次渲染一部分的话,那这样的数据用起来可能就不是那么的舒服,所以我们可以转变一下思路,将其作为二维数组,[[1-10], [11-20]…]例如这样,就比较适合我们进行数据的操作了
处理结果如下:
可以看到,处理的时间还是非常短的,目前我所使用的机器配置还是比较低的,所以不用担心这些数据处理的损耗,如果是这一点都想省去一点的话,就可以每次获取一部分就渲染一部分,再次拿取下一部分在渲染,我这里为了方便,就直接处理了
在完成这个之前,我们首先需要对 render 这个渲染函数进行改造,如下:
function render(list) {
const fragment = document.createDocumentFragment()
list.forEach(item => {
const div = document.createElement('div')
div.textContent = `${item.id}-${item.name}`
div.classList.add('item')
fragment.appendChild(div)
})
box.appendChild(fragment)
}
现在我们只需要一个函数,来帮助我们完成重复执行 render 函数即可,如下:
function exec() {
// 边界判断
if (index >= renderList.length) return
// 使用定时器主要是进入一个异步任务,不阻碍主线程的渲染
setTimeout(() => {
render(renderList[index])
index++
// 再次调用
exec()
}, 0)
}
函数准备完毕之后,我们看一下整体的代码,如下:
const data = []
for (let i = 0; i < 100000; i++) {
data.push({ id: i, name: `Item ${i}` })
}
const renderList = []
// 处理数据
function cutChuck(list, size) {
for (let i = 0; i < list.length; i += size) {
renderList.push(list.slice(i, i + size))
}
}
cutChuck(data, 10)
const box = document.querySelector('.box')
const btn = document.querySelector('.btn')
let index = 0
function exec() {
if (index >= renderList.length) return
setTimeout(() => {
render(renderList[index])
index++
exec()
}, 0)
}
btn.addEventListener('click', function () {
exec()
})
function render(list) {
const fragment = document.createDocumentFragment()
list.forEach(item => {
const div = document.createElement('div')
div.textContent = `${item.id}-${item.name}`
div.classList.add('item')
fragment.appendChild(div)
})
box.appendChild(fragment)
}
执行效果如图:
此时就可以看到,渲染就不会出现一开始那样的长时间的卡顿或白屏,而且滚动条还在自动往上滑动,就可以表示还在不停的渲染
当然如果你的案例觉得卡顿的话,可以去使用 requestAnimationFrame 来减少页面reflow的次数,提升性能,使用也是非常简单的,只是把通过 setTimeout 调用更换一下,具体的分析后续有时间会放在另一篇文章中