React虛擬列表的實現(xiàn)代碼
最近看了vueuse的useVirtualList的實現(xiàn)方式,發(fā)現(xiàn)虛擬滾動效果不錯,就嘗試著同樣的寫法改成react版本。
虛擬列表主要包含三部分組成,示意圖如下:

定義一個 useVirtualList 的 hook 需要接收兩個參數(shù),一是接收所有的數(shù)據(jù),二是定義一些配置參數(shù);需要返回的四個參數(shù),一是當前加載的列表,二是獲取容器的 ref 和容器滾動事件,三是快速定位的方法,四是內(nèi)層容器的樣式?;镜拇a結(jié)構(gòu)如下:
export type UseVirtualListOptions = {
// 如果是每條數(shù)據(jù)的高度相同為number 否則通過方法計算高度
itemHeight: number | ((index: number) => number);
overscan?: number;
};
export const useVirtualList = (
list: AllItems[],
options: UseVirtualListOptions
): UseVirtualListReturn => {
const containerRef = useRef(null);
const [currentList, setCurrentList] = useState<CurrentList[]>([]);
const [wrapperStyle, setWrapperStyle] = useState({
width: "100%",
height: "0px",
marginTop: "0px",
});
return {
list: currentList,
containerProps: {
ref: containerRef,
onScroll: () => {},
},
scrollTop,
wrapperStyle,
};
};因為需要容器的高度和滾動距離來計算加載的內(nèi)容,因此需要在 dom 掛載完成后執(zhí)行。
1、計算容器的的高度能顯示的數(shù)據(jù)
如果 itemHeight 類型為 number,則可以通過容器高度 / itemHeight 來計算容器能顯示多少條數(shù)據(jù),否則需要把上一次開始索引作為起始值,預估滾動之后能夠在可視區(qū)域顯示多少數(shù)據(jù)。
const getViewCapacity = (containerHeight: number) => {
if (typeof itemHeight === "number") {
return Math.ceil(containerHeight / itemHeight);
}
let sum = 0;
let capacity = 0;
const { start = 0 } = state;
console.log(start);
for (let i = start; i < list.length; i++) {
const height = itemHeight(i);
sum += height;
if (sum >= containerHeight) {
capacity = i;
break;
}
}
return capacity - start;
};2、計算滾動偏移的數(shù)據(jù)量
如果 itemHeight 類型為 number,則可以通過容器高度 / itemHeight 來計算滾動偏移的數(shù)據(jù)量,否則需要對滾動偏移的 dom 高度進行累加,計算出偏移的數(shù)據(jù)量。
const getOffset = (scrollTop: number) => {
if (typeof itemHeight === "number") {
return Math.floor(scrollTop / itemHeight) + 1;
}
let sum = 0;
let offset = 0;
for (let i = 0; i < list.length; i++) {
const height = itemHeight(i);
sum += height;
if (sum >= scrollTop) {
offset = i;
break;
}
}
return offset + 1;
};3、計算滾動時需要加載的數(shù)據(jù)
需要加載的數(shù)據(jù)開始位置 = 滾動偏移量 - 預加載的數(shù)量
需要加載的數(shù)據(jù)結(jié)束位置 = 滾動偏移量 + 可視區(qū)域顯示數(shù)據(jù)量 + 預加載數(shù)據(jù)量
如果開始位置 < 0 則從 0 開始,結(jié)束位置大于總數(shù)組長度,則結(jié)束位置為總數(shù)據(jù)的長度
const from = offset - overscan;
const to = offset + viewCapacity + overscan;
state.start = from < 0 ? 0 : from;
state.end = to > list.length ? list.length : to;
setCurrentList(() => {
return list.slice(state.start, state.end).map((ele, index) => ({
data: ele,
index: index + state.start,
}));
});4、計算列表容器的高度和滾動的高度
列表容器的高度 = 數(shù)據(jù)長度 * 預估每條數(shù)據(jù)的高度 - 滾動偏移的高度
因為加載的數(shù)據(jù)不是每次都從 0 開始,但每次渲染是從頂部開始的,所以滾動到高度大于數(shù)據(jù)的高度時,數(shù)據(jù)位于可視區(qū)域的上方,此時需要設(shè)置 marginTop,讓加載的數(shù)據(jù)顯示在可視區(qū)域的頂部,但如果總高度不變,數(shù)據(jù)全部加載完成后,底部會有大面積留白。
const computedWrapperStype = () => {
const offsetTop = getDistanceTop(state.start);
setWrapperStyle({
width: "100%",
height: `${totalHeight() - offsetTop}px`,
marginTop: `${offsetTop}px`,
});
};5、添加快速定位功能
當在輸入框數(shù)據(jù)要定位的索引值時,可以根據(jù)索引值計算出可視區(qū)域上方需要偏移的高度,然后重新調(diào)用計算需要加載數(shù)據(jù)開始、結(jié)束位置的方法,即可實現(xiàn)快速定位。
const scrollTop = (index: number) => {
if (containerRef.current) {
(containerRef.current as HTMLDivElement).scrollTop = getDistanceTop(index);
calculateRange();
}
};6、源碼地址
https://stackblitz.com/edit/vitejs-vite-oy7obm?file=src%2FApp.tsx
到此這篇關(guān)于React虛擬列表的實現(xiàn)代碼的文章就介紹到這了,更多相關(guān)React虛擬列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何用webpack4.0擼單頁/多頁腳手架 (jquery, react, vue, typescript)
這篇文章主要介紹了如何用webpack4.0擼單頁/多頁腳手架 (jquery, react, vue, typescript),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06
React中memo useCallback useMemo方法作用及使用場景
這篇文章主要為大家介紹了React中三個hooks方法memo useCallback useMemo的作用及使用場景示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-03-03
React如何使用create-react-app創(chuàng)建react項目
這篇文章主要介紹了React如何使用create-react-app創(chuàng)建react項目問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

