接下来我会为你详细讲解“利用React实现虚拟列表的示例代码”的攻略。
什么是虚拟列表
虚拟列表是一种优化长列表性能的方式,通常用于在前端渲染海量数据的场景。它的基本思路是只渲染可见区域的数据,而不是渲染整个列表内容。
如何利用React实现虚拟列表
以下是基于React实现虚拟列表的示例代码及其详细讲解:
步骤一:设置列表数据和列表项高度
根据虚拟列表的思路,首先需要确定显示列表的可见部分高度和单个列表项的高度。我们以一个10000个数据项的列表为例:
const ITEM_HEIGHT = 50; // 列表项高度
const TOTAL_COUNT = 10000; // 数据总数
// 构造数据源
const data = new Array(TOTAL_COUNT).fill('').map((_, index) => {
return `第${index}项`;
});
步骤二:使用useRef和useState存储可视区域的数据
下一步,我们需要根据滚动位置来计算哪些数据需要被渲染。因为需要在render之间保存数据,我们需要使用React的useRef和useState来保存数据:
const [start, setStart] = useState(0); // 可视区域的起始位置
const [end, setEnd] = useState(10); // 可视区域的结束位置
// 使用useRef来保存DOM节点和scrollTop
const listRef = useRef(null);
const scrollTopRef = useRef(0);
const visibleData = useMemo(() => {
// 这里的slice是为了复制一份只包含可视区域的数据,避免直接使用data.slice(start, end)导致原始data被修改的问题
return data.slice(start, end);
}, [start, end]);
步骤三:渲染列表项
现在我们可以将可视区域内的数据渲染出来了。在这里,我们遍历visibleData生成对应的列表项:
<div style={{ height: `${ITEM_HEIGHT * count}px` }}>
{visibleData.map((item, index) => {
return (
<div key={start + index} style={{
height: `${ITEM_HEIGHT}px`,
position: 'absolute',
top: `${(start + index) * ITEM_HEIGHT}px`,
width: '100%',
}}>
{item}
</div>
);
})}
</div>
步骤四:监听滚动事件,更新可视区域数据
为了实现滚动和更新可视区域数据,我们需要在列表外层包裹一个div,并给它设置固定高度和overflow: auto属性。通过监听这个div的scroll事件,我们可以根据它的scrollTop来计算可视区域的起始和结束位置:
const handleScroll = useCallback(
throttle(() => {
const listEl = listRef.current;
const scrollTop = listEl.scrollTop;
scrollTopRef.current = scrollTop;
setStart(Math.floor(scrollTop / ITEM_HEIGHT));
setEnd(Math.ceil((scrollTop + window.innerHeight) / ITEM_HEIGHT));
}, 166), // 166ms 节流,避免在滚动过程中频繁计算
[]
);
<div ref={listRef} style={{
height: '500px', // 设置为可视区域高度即可
overflow: 'auto',
}} onScroll={handleScroll}>
<div style={{ height: `${ITEM_HEIGHT * TOTAL_COUNT}px` }}>
{visibleData.map((item, index) => {
return (
<div key={start + index} style={{
height: `${ITEM_HEIGHT}px`,
position: 'absolute',
top: `${(start + index) * ITEM_HEIGHT}px`,
width: '100%',
}}>
{item}
</div>
);
})}
</div>
</div>
示例说明
以上是基于React实现虚拟列表的完整攻略,现在我们来看两个进一步的示例说明:
示例一:使用IntersectionObserver实现可视区域的监听
在上面的示例中,我们在scroll滚动事件中计算了可视区域的起始和结束位置。但是,这种做法会在scroll滚动时触发大量的事件,降低性能。为了避免这个问题,我们可以使用IntersectionObserver API来监听可视区域变化,减少事件的触发次数。以下是示例代码:
const [intersectingStart, setIntersectingStart] = useState(0); // 相交区域的起始位置
const [intersectingEnd, setIntersectingEnd] = useState(10); // 相交区域的结束位置
const handleObserveChange = useCallback((entries) => {
for (let entry of entries) {
const index = entry.target.dataset.index * 1;
if (entry.isIntersecting) {
if (index < intersectingStart) {
setIntersectingStart(index);
}
if (index > intersectingEnd) {
setIntersectingEnd(index);
}
} else {
if (index === intersectingStart) {
setIntersectingStart(index + 1);
}
if (index === intersectingEnd) {
setIntersectingEnd(index - 1);
}
}
}
}, [intersectingStart, intersectingEnd]);
<div ref={listRef} style={{
height: '500px',
overflow: 'auto',
}} onScroll={handleScroll}>
<div ref={visibleRef} style={{ height: `${ITEM_HEIGHT * TOTAL_COUNT}px` }}>
{data.map((item, index) => {
return (
<div
key={index}
data-index={index}
style={{
height: `${ITEM_HEIGHT}px`,
position: 'absolute',
top: `${index * ITEM_HEIGHT}px`,
width: '100%',
}}
ref={index >= intersectingStart && index <= intersectingEnd ? observerRef : null}
>
{item}
</div>
);
})}
</div>
</div>
以上代码中,我们使用useRef在可视区域内的节点中遍历检测是否进入了可视区域,利用useState记录可视区域的起始位置和结束位置。
示例二:使用缓存技术进一步优化性能
在示例一中,我们已经使用IntersectionObserver API来监听可视区域变化,但是节点的渲染仍然有性能瓶颈。这是因为在动态生成节点时,我们每次需要重新创建并渲染整个节点。
为了解决这个问题,可以使用React的缓存机制,存储可视区域内的节点。在下一次滚动时,我们仅需更新可视区域的样式即可,从而大幅减少渲染节点的时间和性能开销。以下是示例代码:
const ITEM_CACHE = new Map(); // 缓存可视区域内的节点
const CACHE_SIZE = 50; // 缓存的节点数
function getRenderList(start, end) {
const renderList = [];
for (let i = start; i <= end; i++) {
if (ITEM_CACHE.has(i)) {
renderList.push(ITEM_CACHE.get(i));
} else {
const item = (
<div
key={i}
data-index={i}
style={{
height: `${ITEM_HEIGHT}px`,
position: 'absolute',
top: `${i * ITEM_HEIGHT}px`,
width: '100%',
}}
ref={start <= i && i <= end ? observerRef : null}
>
{data[i]}
</div>
);
renderList.push(item);
ITEM_CACHE.set(i, item);
if (ITEM_CACHE.size > CACHE_SIZE) {
ITEM_CACHE.delete(ITEM_CACHE.keys().next().value);
}
}
}
return renderList;
}
<div ref={listRef} style={{
height: '500px',
overflow: 'auto',
}} onScroll={handleScroll}>
<div style={{ height: `${ITEM_HEIGHT * TOTAL_COUNT}px` }}>
{getRenderList(start, end)}
</div>
</div>
以上代码中,我们使用Map缓存已经渲染过的可视区域内的节点。如果缓存超过了CACHE_SIZE就清除缓存中最早的节点。在每次渲染节点时,先从缓存中查找是否已经渲染过,避免重复创建渲染。如果缓存中没有找到可重用的节点,再创建新的节点并存储在缓存中。这样在滚动过程中只需要更新可视区域内已经存在的节点,而无需重新创建和渲染整个节点,从而大幅提升了性能表现。
以上就是利用React实现虚拟列表的完整攻略,并给出了两个进一步的示例说明。希望能对你有所帮助!
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:利用React实现虚拟列表的示例代码 - Python技术站