简单聊一下virtual-list
2020-07-16 本文已影响0人
小进进不将就
原理
比如现在有一千条数据,生成 DOM 实例并渲染到页面上,是非常卡的:
export default function App() {
let i=0
const divArr=[]
while(i<=1000){
divArr.push(<div key={i} style={{border:'1px black solid',height:20,}}>{i}</div>)
i++
}
return (
<div style={{height:300,}}>
{divArr}
</div>
);
}
virtual-list 的原理就是只渲染出可视区域的数据,而不可见的数据用空白元素填充,同时滚动条用假滚动,让用户认为是列表滚动以显示数据的:
换句话叫按需加载,同时缓存已加载的数据
基本实现代码
export default function App() {
const all = 1000
//每个 item 高度
const divHeight = 20
//显示 item 的数量
const divNum = 15
const [divArr, setDivArr] = React.useState([])
const [startNum, setStartNum] = React.useState([])
return (
<div style={{
marginTop:30,
height: 300,
width: 300,
overflow: 'auto',
position: 'relative',
border: '1px solid red',
textAlign: 'center',
left: '50%',
}}
onScroll={(e) => {
const newStartNum = Math.ceil(e.target.scrollTop / divHeight)
const startHeight = newStartNum * divHeight
const endNum = newStartNum + divNum
const endHeight = startHeight + divNum * divHeight
const newDivArr = []
//start 和 end 都再多渲染一条,不然往下滚动出现白色间隙不好看
for (let i = newStartNum ; i < endNum; i++) {
//起始位置多加一条
if (i === newStartNum) {
newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
key={i-1}>{i-1}</div>)
}
newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
key={i}>{i}</div>)
//结束位置多加一条
if (i === endNum) {
newDivArr.push(<div style={{backgroundColor: '#4a7bf7', height: divHeight, paddingRight: 20,}}
key={i}>{i}</div>)
}
}
setDivArr(newDivArr)
setStartNum(newStartNum)
}}
>
{/*空白元素的高度,目的是让滚动条 scroll*/}
<div style={{
height: (startNum - 1) * divHeight,
}}>
</div>
{/*实际渲染的列*/}
<div style={{
height: (all - startNum + 1) * divHeight,
}}>
{divArr}
</div>
</div>
);
}
缓存
可通过一个cacheObject
来保存 data[i] 的 value、height(根据 value 的长度计算)、offsetY(根据 height 和 index 来计算)(index 为 key),在循环渲染固定数量的 item 时,先从cacheObject
中根据 index 判断该 item 是否存在,存在即从cacheObject
中获取,否则需要计算item
的height
和offsetY
session 最大可存储5mb数据,大约是 250 万个字符,绰绰有余,使用频繁的话,localstorage更好
搜索算法
用户是不可能一直往下翻,来 select 的,所以搜索功能是必须的。
注意:
下面两种方法只适用于有序的 number 数组
二分法
function middleSearch(arr, start, end, value) {
//用户输入的 value
// let value = -1
// let start = 0
// let end = arr.length - 1
let middle
while (start <= end) {
//中间值
middle = Math.floor((start + end) / 2)
//如果输入的值正好是中间值的话
if (value === arr[middle]) {
//终止循环
return middle
} else if (arr[middle] > value) {
end = middle - 1
} else {
start = middle + 1
}
}
if (!middle) {
middle = -1
}
return middle
}
搜索指数法
简单说是游标的 index 不断乘 2,直到 arr[index] >= 查找的 value,此时就确定了 index 的范围,进而用二分法解决,看代码就懂了:
//=============指数搜索====================
// Returns position of first occurrence of
// x in array
//arr:[2, 3, 4, 10, 20,30,40]
//length:5
//value:10
function exponentialSearch(arr, length, value) {
//先判断 data 的第一个 value 是否是要查找的 value
//因为指数搜索要从 1 开始( 0*2 一直是 0)
if (arr[0] === value) {
return 0;
}
//从 index=1 开始,查找范围
let start = 1;
let end = 1;
//i 最终为 4
while (end < length && arr[end] <= value) {
start = start * 2;
end = end * 2;
}
//确定查找范围是 index 2~4 后,在该范围内进行「二分」查找
//注意:
//(1) 由于是先 *2 再判断范围,所以当条件成立后,start 必定是多乘了 2 的
//(2) 由于是先 *2 再判断范围,所以end 可能会超出 length
return middleSearch(arr, start / 2, Math.min(end, length), value);
}
function main() {
//data 数组(numnber 类型的递增数组)
const arr = [2, 3, 4, 10, 40]
//用户输入的值
const value = 10;
//查找的结果(返回下标)
//指数搜索
const result = exponentialSearch(arr, arr.length, value);
console.log(result < 0 ? "Element is not present in array" : "Element is present at index " + result);
}
main()
仍然不理解的话,请看:https://www.geeksforgeeks.org/exponential-search/
最后
感兴趣,并且想深入理解的话,建议看下源码:
https://github.com/react-component/virtual-list
现在项目业务上没有用到 virtual-list,所以我暂时不解析该源码了。
(完)