【前端面试】前端性能优化之虚拟滚动
前言
又到了金三银四的时间,相信这个时候会有一部分小伙伴因为种种原因打算跳槽的。就算是不打算跳槽的小伙伴也会观望一下行情,其实不管有没有跳槽的打算偶尔去面试一下也会对自己的成长有所帮助的。通过面试我们可以了解当前流行的技术,同时在面试过程中我们也会知道自己自己的短板,特别是本身技术不是很精湛且在公司没有人带的小伙伴。
我本人就是一个例子,我目前所在的公司因为某些原因,导致基本没有人带新手,我入职在22年毕业入职之后基本都是靠自己摸索,学习技术基本都是自己在网上看一些视频,或者买一些书籍来看。但没有人指导有时候也会进入某个误区。例如我在某次面试中,面试官问了我一个关于性能优化的问题,我当时回答是我在项目中做的一个学习资料的页面,当学习资料的数据很多的时候请求接口时接口返回数据很慢,所以我与后端商量请求接口是加两个参数,每次请求返回多少条数据以及返回那些参数。这样请求接口就会很快。但其实当浏览器渲染的数据多了也会造成页面的卡顿,所以当时面试官问我了解虚拟滚动吗?我回答没有听说过。
虚拟滚动
使用背景
前端开发中,当我们需要展示大量数据时,如果我们把所有的元素都渲染出来的话就会造成以下问题:
1、大量的数据需要庞大的DOM元素来呈现,这会导致浏览器的渲染时间明显增加,影响了页面的加载和交互性能。
2、每个列表项都在内存中占用一定的空间,当数据量庞大时会占用大量的内存,占用的内存过多时可能会导致计算机运行缓慢或出现卡顿甚至是崩溃等问题。
3、影响用户体验,一次性加载庞大数据需要较长的时间,这使得用户在等待数据加载完成时可能会感到不耐烦,从而影响了整体的用户体验。
虚拟滚动列表
既然渲染大量的数据会造成那么多问题,那么我们有什么办法去解决这些问题呢?这个肯定是有的,答案就是:列表局部渲染,又被称为虚拟列表
虚拟滚动列表通过只渲染可视区域的列表项,当用户滚动时,动态计算可视区域的起始索引,然后只渲染这部分列表项,避免了一次性加载大量数据从而实现平滑的滚动效果,并且列表项移出可见区域时,虚拟滚动列表会回收对应的DOM元素从而降低内存占用。
虚拟滚动列表的实现原理通常包括以下几个步骤:
- 计算可视区域的高度以及每个列表项的高度。
- 根据滚动条的位置,计算出可视区域的起始索引和结束索引。
- 只渲染起始索引和结束索引之间的列表项。
- 当滚动条位置变化时,重新计算起始索引和结束索引,并更新渲染的列表项。
代码实现:
import {useState,useEffect} from "react"
import './home.css'
function Home(){
const [dataTotal,setDataTotal] = useState(100) // 记录列表的数量
const itemHeight = 30 // 单个元素的高度
const marginBottom = 10 // 边距
const containerHeight = 400 //窗口的高度
const [showDataNumber] = useState(Math.round(containerHeight / (itemHeight + marginBottom) )) //渲染数组的数量
const [list,setList] = useState(Array.from({ length: dataTotal }, (_, index) => index + 1)) // 模拟后端返回的数据
const [showData, setShowData] = useState(list.slice(0, showDataNumber)) // 渲染的数据
const [addData,setAddData] = useState(false) //是否滚动到底部
useEffect(()=>{
setTimeout(()=>{
if(addData){ // 触底请求接口,往已有的数据push接口的元素
let num = dataTotal + 1
let arr = new Array(100).fill().map(()=>num++)
setDataTotal(dataTotal+100)
setList([
...list,
...arr
])
setAddData(false)
}
},1000)
},[addData])
const onScrollContainer = (e) => {
const scrollTop = e.currentTarget.scrollTop
let start = Math.round(scrollTop / (itemHeight + marginBottom))
let end = Math.round((scrollTop + containerHeight) / (itemHeight + marginBottom) )
setShowData(list.slice(start, end)) // 根据滚动的高度切片选择渲染的数据
if(end === list.length && dataTotal < 1000){
setAddData(true)
setShowData(list.slice(start, end-1)) //减一显示loading(在实际开发中可以是UI插件的loading)
}
}
return (
<div>
<div className="view-container">
<div onScroll={onScrollContainer} className="scorll-container">
<div style={{ height: list.length * (itemHeight + marginBottom)}}>
</div>
</div>
</div>
<div className="item-container">
{ showData.map(item => {
return <div style={{ height: itemHeight,marginBottom:marginBottom}} className="item" key={item}>{item}</div>
}) }
{addData && <div>loading...</div>}
</div>
</div>
)
}
export default Home
/* home.css */
.view-container{
height: 400px;
width: 400px;
position: fixed;
overflow-y: hidden;
border: 1px solid black;
}
.scorll-container{
height: 400px;
overflow-y: scroll;
}
.item-container{
height: 400px;
overflow-y: hidden;
}
.item{
background-color: rgba(254,226,55, 0.5);
width: 400px;
}
小结
本文主要讲述了我在面试中的收获,正所谓三人行,必有我师焉,有时候我们埋头研究也不如与别人探讨交流收获来得快!在金三银四的日子里希望大家都有所收获!
转载自:https://juejin.cn/post/7337170233604620351