高效渲染大量数据,数据无感刷新

100 阅读3分钟

首先我们得清楚数据量大导致哪些问题

  • 页面加载时间过长,导致浏览器无响应
  • 数据量大操作渲染时 DOM 操作过长页面卡顿
  • 频繁大量数据可能会导致爆内存
  • 查看、操作数据乱用户体验差
  • 影响 SEO 优化

解决方案

  • 虚拟列表
  • 分页加载
  • 懒加载
  • 异步加载
  • 线程加载
  • 文档碎片
  • freeze
  • requestAnimationFrame
  • 其他资源

虚拟列表

定义:是一种按需加载的实现方式,对可见区域进行渲染,对非可视区域中的数据不渲染或部分渲染,从而提高渲染性能。
如:可见高度为500px,每条列表项50px,那么每次就最多渲染10个列表项。

let clientHeight = 500, itemHeight = 50
const data = Array(10000).fill({}).map((i,idx)=>({content: `${idx}数据`}))
const init = ()=>{
    const renderContent = clientHeight / itemHeight
    render(Math.floor(renderContent))
}
const render = (height, start=0)=>{
    const renderContent = height / itemHeight
    const frame = document.createDocumentFragment()
    for(let i=start, i<Math.floor(renderContent), i++){
        const temDom = Document.createElement("div")
        tempDom.innerHTML = data[i].content
        frame.appendChild(tempDom)
    }
    container.innerHTML = ""
    container.appendChild(frame)
}
Element.addEventLiistener("scroll", (event)=>{
    event = event.target
    if(event.scrollTop){
        // push操作
        render(Math.floor(event.scrollTop + clientHeight))
    }
})
init()

分页加载

定义:对数据每次渲染特定条数,减少全部渲染的重排,从而提高性能。
如:分页pageSize: 20, page: 1

const pagination = { pageSize: 20, page: 1 }
// 默认渲染20条
...
// 分页操作
buttom.addEventListener("click", (event)=>{
    event = event.target
    value = event.innerText
    pageination.page = value
    container.innerHTML = ""
    render(20, (value-1)*pagination.pageSize)
}

懒加载

定义:使用 settimeout 去实现懒加载(不推荐),除非是在静态资源的加载。
建议不要在工作中使用,它只是把加载工作给延迟了。

Vue 代码
// 模板语法
<div v-for="(item,key) in applyList" :key="key"> ... </div>

onMounted(()=>{
    setTimeout(()=>{
        applyList.value = data
    })
})

异步加载

定义:使用 ES6 async 去实现异步加载,不阻塞当前线程(不推荐),推荐使用异步加载配合文档碎片实现。

const rederAll = async()=>{
    const batch = 100
    const frame = document.createDocumentFragment()
    const createEl = ()=>{
        for(let i=0; i<100; i++){
            const div = document.createElement("div")
            div.innerHTML = "xxx"
            frame.appendChild(div)
        }
    }
    for(let i=100; i<10000; i+=100){
        createEl()
    }
}

线程加载

定义:在Web worker处理大量数据操作。

const worker = new Worker(render.js)
worker.addEventListener("message", ({data})=>{
    container.appendChild(data)
})
// render.js
self.postMessage(处理之后的数据)

文档碎片

定义:推荐在原生的开发环境下使用,通过创建文档碎片来避免过渡的回流去操作DOM实现渲染。
作用:减少浏览器重排,通过创建一个浏览器容器对象,来添加DOM操作,统一进行添加至DOM树。

	let batchSize = 100
	let num = batchSize
	const body = document.body
	const fragment = document.createDocumentFragment()
	for (let i = 0; i < num; i++) {
	        const div = document.createElement('div')
	        div.innerText = 111111111
	        fragment.append(div)
	}
	body.appendChild(fragment)
	
	const fragment1 = document.createDocumentFragment()
	const temp = setInterval(() => {
	        if (num >= len) {
	                clearInterval(temp)
	                document.body.appendChild(fragment1)
	        }
	        for (let i = 0; i < batchSize; i++) {
	                const div = document.createElement('div')
	                div.innerText = 22222
	                fragment1.append(div)
	        }
	        num += batchSize
	})

freeze

定义:在Vue或者React中使用导致数据被代理或绑定,会影响性能,使用freeze冻结对象使得对象只读。
场景:使用freeze结对象可以防止触发代理的setter和getter。

// Vue
const list = ref([])
Object.freeze(list.value)
// React
const [list, setList] = useState([])
Object.freeze(list)

requestAnimationFrame

定义:浏览器执行动画的方法,在下次重绘之前调用,不会触发回流。

const myRender = ()=>{
    render()
    if(条件){
        requestAnimation(myRender)
    }
}
requestAnimation(myRender)

其他资源

在商城后台管理系统中,列表大概率会出现商品图片,这时就可以结合上面的解决方案来实现。\

  • 图片压缩,使用webp压缩图片大小
  • lazy加载
  • 后端定义响应头缓存图片
  • 小图标可以使用精灵图或者字体图表来标识