102 lines
2.2 KiB
TypeScript
102 lines
2.2 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
||
|
||
interface UseInfiniteScrollOptions {
|
||
/**
|
||
* 是否有更多数据可以加载
|
||
*/
|
||
hasNextPage: boolean;
|
||
/**
|
||
* 是否正在加载
|
||
*/
|
||
isLoading: boolean;
|
||
/**
|
||
* 加载下一页的函数
|
||
*/
|
||
fetchNextPage: () => void;
|
||
/**
|
||
* 触发加载的阈值(px),当距离容器底部多少像素时触发加载
|
||
*/
|
||
threshold?: number;
|
||
/**
|
||
* 是否启用(默认为true)
|
||
*/
|
||
enabled?: boolean;
|
||
}
|
||
|
||
/**
|
||
* 无限滚动Hook
|
||
* 支持向下滚动和向上滚动加载更多
|
||
*/
|
||
export function useInfiniteScroll({
|
||
hasNextPage,
|
||
isLoading,
|
||
fetchNextPage,
|
||
threshold = 200,
|
||
enabled = true,
|
||
}: UseInfiniteScrollOptions) {
|
||
const [isFetching, setIsFetching] = useState(false);
|
||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||
|
||
// 加载更多数据
|
||
const loadMore = useCallback(async () => {
|
||
if (!hasNextPage || isLoading || isFetching) return;
|
||
|
||
setIsFetching(true);
|
||
try {
|
||
fetchNextPage();
|
||
} finally {
|
||
// 延迟重置状态,避免快速重复触发
|
||
setTimeout(() => {
|
||
setIsFetching(false);
|
||
}, 100);
|
||
}
|
||
}, [hasNextPage, isLoading, isFetching, fetchNextPage]);
|
||
|
||
// 设置Intersection Observer
|
||
useEffect(() => {
|
||
if (!enabled || !loadMoreRef.current) return;
|
||
|
||
const options = {
|
||
root: null,
|
||
rootMargin: `${threshold}px`,
|
||
threshold: 0.1,
|
||
};
|
||
|
||
observerRef.current = new IntersectionObserver(
|
||
(entries) => {
|
||
const [entry] = entries;
|
||
if (entry.isIntersecting) {
|
||
loadMore();
|
||
}
|
||
},
|
||
options
|
||
);
|
||
|
||
const currentRef = loadMoreRef.current;
|
||
if (currentRef) {
|
||
observerRef.current.observe(currentRef);
|
||
}
|
||
|
||
return () => {
|
||
if (observerRef.current && currentRef) {
|
||
observerRef.current.unobserve(currentRef);
|
||
}
|
||
};
|
||
}, [enabled, threshold, loadMore]);
|
||
|
||
// 清理observer
|
||
useEffect(() => {
|
||
return () => {
|
||
if (observerRef.current) {
|
||
observerRef.current.disconnect();
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
return {
|
||
loadMoreRef,
|
||
isFetching,
|
||
loadMore,
|
||
};
|
||
}
|