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

102 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
}