文章目录
- 直接渲染
- 数据的拆分
- 使用定时器分页渲染
相信有一道耳熟能详的题目,如果前端获取到了 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 = 0function exec() {if (index >= renderList.length) returnsetTimeout(() => {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 调用更换一下,具体的分析后续有时间会放在另一篇文章中