摘要
基于react实现的固定高度的虚拟列表,使用相对较小的代码量,聚焦核心逻辑,方便小白学习了解原理
一、效果
二、代码
import { throttle } from 'lodash';
import React, { useMemo, useState } from 'react';
interface typeProps {
/** 全部的数据 */
list: { name: string; key: string }[];
}
const HEIGHT = 100; // 每一行元素的渲染高度,先讨论固定高度,动态高度后续另外开文章
const PAGE_COUNT = 10; // 每一页的渲染条数, 灵活一点的话,可以结合可视区高度除以HEIGHT动态计算
const Vl = (props: typeProps) => {
const alllist = props.list || [];
/** 跟踪滚动高度 */
const [scrollTop, setScrollTop] = useState(0);
/** 关注scrollTop的变化,然后计算切片开始的位置 */
const startIndex = useMemo(() => {
/** 当前滚动高度除以每个元素的高度,向下取整 */
let index = Math.floor(scrollTop / HEIGHT);
// // 满足条件下,往前多渲染两个,解决留白问题
if (index > 10) {
return index - 3;
}
return index;
}, [scrollTop]);
/** 结束位置直接根据开始位置加上PAGE_COUNT计算,再处理一下数据越界 */
const endIndex = useMemo(() => {
if (startIndex + PAGE_COUNT >= alllist.length) {
return alllist.length;
} else {
return startIndex + PAGE_COUNT + 1;
}
}, [startIndex, alllist.length]);
/** 开始和结束位置都计算好了,准备对原始数据进行切片 */
const renderList = alllist.slice(startIndex, endIndex);
/** 最后,需要注意,1000个元素和切片出来的10个元素能撑开的高度是不一样的,需要再计算一下上下的差距,用padding撑开滚动区域 */
const paddingTop = startIndex > 0 ? startIndex * HEIGHT : 0;
const paddingBottom =
endIndex < alllist.length - 1
? (alllist.length - endIndex - 1) * HEIGHT
: 0;
/** 监听处理滚动事件,记得加个节流 */
const onScroll = throttle(
(e) => {
setScrollTop(e.target.scrollTop);
},
200,
{
leading: true,
trailing: false,
},
);
return (
<div>
<div>
{/* 变量观测 */}
{startIndex} - {endIndex} - 总计: {alllist.length}, 滚动高度:{' '}
{scrollTop}
</div>
<div
style={{ border: '1px solid red', height: '400px', overflowY: 'auto' }}
onScroll={onScroll}
>
{/* 用于撑开上面滚动条空间 */}
<div style={{ paddingTop: paddingTop + 'px' }}></div>
{/* 切片渲染部分 */}
{renderList.map((item) => {
return (
<div
style={{ height: '100px', margin: '2px', background: '#eee' }}
key={item.key}
>
<div>{item.name}</div>
</div>
);
})}
{/* 用于撑开下面滚动条空间 */}
<div style={{ paddingBottom: paddingBottom + 'px' }}></div>
</div>
</div>
);
};
export default React.memo(Vl);
三、测试
import Vl from './vl';
const VlistTest = () => {
const list = Array(1000)
.fill(1)
.map((item, index) => {
return {
name: 'name' + index,
key: 'key_' + index,
};
});
return <Vl list={list} />;
};
export default VlistTest;