crush-level-web/src/hooks/useInfiniteScroll.ts

102 lines
2.2 KiB
TypeScript
Raw Normal View History

2025-11-13 08:38:25 +00:00
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,
};
}