React 虚拟滚动 长列表

发布时间 2023-09-20 19:51:52作者: 小龙的无所事事

定高版本

 1 "use client";
 2 import React, { useCallback, useMemo, useState } from "react";
 3 
 4 interface IProps {
 5   list: any[];
 6   fixedHeight: number;
 7 }
 8 
 9 // Fixed height
10 const VirtualView = (props: IProps) => {
11   const { list, fixedHeight } = props;
12 
13   const [startIndex, setStarIndex] = useState(0);
14   const [endIndex, setEndIndex] = useState(10);
15 
16   const targetList = list.slice(startIndex, endIndex);
17   const visibleNum = targetList.length;
18 
19   const { containerHeight, viewHeight } = useMemo(() => {
20     return {
21       viewHeight: visibleNum * fixedHeight,
22       containerHeight: list.length * fixedHeight,
23     };
24   }, [list, fixedHeight, visibleNum]);
25   console.log(startIndex, endIndex, visibleNum);
26 
27   const maxIndex = list.length - visibleNum;
28   const handleScroll = useCallback(
29     (e: any) => {
30       const scrollTop = e?.target?.scrollTop as number;
31       let nextIndex = Math.floor(scrollTop / fixedHeight);
32       nextIndex = Math.min(maxIndex, nextIndex);
33       setStarIndex(nextIndex);
34       setEndIndex(visibleNum + nextIndex);
35     },
36     [fixedHeight, setStarIndex, setEndIndex, maxIndex, visibleNum]
37   );
38   return (
39     <div
40       onScroll={handleScroll}
41       className={`virtual-list-view relative w-1/2 overflow-y-auto border-2 border-l-indigo-800`}
42       style={{ maxHeight: viewHeight }}
43     >
44       <div className="list-phantom" style={{ height: containerHeight }}></div>
45       <div className="absolute top-0" style={{ top: startIndex * fixedHeight }}>
46         {targetList.map((item) => {
47           return (
48             <div key={item.id} className={""} style={{ height: fixedHeight }}>
49               <span className="name">{item.name}</span>
50               <p className="">{item.body}</p>
51             </div>
52           );
53         })}
54       </div>
55     </div>
56   );
57 };
58 
59 export default VirtualView;

展示元素个数: visibleNum = enindex - startIndex

组件容器:virtual-list-view

因为每行item高度固定, 可以计算的到容器的高度fixedHeight * visibleNum

并注入onscroll回调,let nextIndex = Math.floor(scrollTop / fixedHeight);  根据scrollTop 距离计算当前应该展示的startIndex 和 endIndex

 

支撑容器高度的元素: list-phantom
根据 list.length * fixedHeight 一开始就能得到整个容器的高度
 
 
列表元素, 根据startIndex 和 endIndex 对数组数据做切分, 获得应该展示的元素数据, 其实在最后start, end 前后可以再多展示几行数据做缓冲, 避免渲染不连续, 嫌麻烦, 这里就不做了
  const targetList = list.slice(startIndex, endIndex);

 

不定高度的版本