This article is the seventeenth of a series of articles explaining the source code of ahoos in simple language, which has been organized into document- address . I think it's not bad, give a star to support it, thanks.
Introduction
useInfiniteScroll encapsulates common infinite scrolling logic.
See the official website for details
Note: Infinite scrolling here refers to the common click to load more or pull-down to load more functions, rather than virtual scrolling, which will be discussed later.
Implementation principle
Implementation principle: The useRequest hook is used to request background data. Among them, reloadAsync corresponds to runAsync of useRequest, and reload corresponds to run of useRequest. The former returns Promise and needs to handle exceptions by itself. The latter has already done exception handling internally.
In addition, if the target and isNoMore parameters are passed in, by listening to the scroll event, determine whether to scroll to the specified position (supports setting the threshold value - the distance threshold from the bottom), and automatically initiate more requests to load, so as to achieve the effect of scrolling automatic loading.
After talking about the principle, let's look at the code.
Implementation
For input parameters and state definitions, you can directly see the comments:
const useInfiniteScroll = <TData extends Data>(
// 请求服务
service: Service<TData>,
options: InfiniteScrollOptions<TData> = {},
) => {
const {
// 父级容器,如果存在,则在滚动到底部时,自动触发 loadMore。需要配合 isNoMore 使用,以便知道什么时候到最后一页了。
target,
// 是否有最后一页的判断逻辑,入参为当前聚合后的 data
isNoMore,
// 下拉自动加载,距离底部距离阈值
threshold = 100,
// 变化后,会自动触发 reload
reloadDeps = [],
// 默认 false。 即在初始化时自动执行 service。
// 如果设置为 true,则需要手动调用 reload 或 reloadAsync 触发执行。
manual,
// service 执行前触发
onBefore,
// 执行后
onSuccess,
// service reject 时触发
onError,
// service 执行完成时触发
onFinally,
} = options;
// 最终的数据
const [finalData, setFinalData] = useState<TData>();
// 是否loading more
const [loadingMore, setLoadingMore] = useState(false);
// 省略代码...
};
Determine whether there is data: the input parameter of isNoMore is the current aggregated data.
// 判断是否还有数据
const noMore = useMemo(() => {
if (!isNoMore) return false;
return isNoMore(finalData);
}, [finalData]);
Processing the request through useRequest, you can see that onBefore, onSuccess, onError, onFinally, manual and other parameters are directly passed to useRequest.
// 通过 useRequest 处理请求
const { loading, run, runAsync, cancel } = useRequest(
// 入参,将上次请求返回的数据整合到新的参数中
async (lastData?: TData) => {
const currentData = await service(lastData);
// 首次请求,则直接设置
if (!lastData) {
setFinalData(currentData);
} else {
setFinalData({
...currentData,
// service 返回的数据必须包含 list 数组,类型为 { list: any[], ...rest }
// @ts-ignore
list: [...lastData.list, ...currentData.list],
});
}
return currentData;
},
{
// 是否手动控制
manual,
// 请求结束
onFinally: (_, d, e) => {
// 设置 loading 为 false
setLoadingMore(false);
onFinally?.(d, e);
},
// 请求前
onBefore: () => onBefore?.(),
// 请求成功之后
onSuccess: d => {
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
scrollMethod();
});
onSuccess?.(d);
},
onError: e => onError?.(e),
},
);
loadMore/loadMoreAsync and reload/reloadAsync respectively call the run and runAsync functions of useRequest.
// 同步加载更多
const loadMore = () => {
// 假如没有更多,直接返回
if (noMore) return;
setLoadingMore(true);
// 执行 useRequest
run(finalData);
};
// 异步加载更多,返回的值是 Promise,需要自行处理异常
const loadMoreAsync = () => {
if (noMore) return Promise.reject();
setLoadingMore(true);
return runAsync(finalData);
};
const reload = () => run();
const reloadAsync = () => runAsync();
And when the reloadDeps dependency changes, reload will be triggered to reset:
useUpdateEffect(() => {
run();
}, [...reloadDeps]);
The last is the logic of scrolling auto-loading. It is judged whether the bottom is reached by the result of scrollHeight - scrollTop <= clientHeight + threshold.
// 滚动方法
const scrollMethod = () => {
const el = getTargetElement(target);
if (!el) {
return;
}
// Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。
const scrollTop = getScrollTop(el);
// Element.scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。
const scrollHeight = getScrollHeight(el);
// 这个属性是只读属性,对于没有定义CSS或者内联布局盒子的元素为0,否则,它是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
const clientHeight = getClientHeight(el);
// 根据上面三个值以及 threshold 判断是否进行加载更多
if (scrollHeight - scrollTop <= clientHeight + threshold) {
loadMore();
}
};
// 监听滚动事件
useEventListener(
'scroll',
() => {
if (loading || loadingMore) {
return;
}
scrollMethod();
},
{ target },
);
The three important values mentioned above, scrollTop, scrollHeight, and clientHeight correspond to the following results:
The Element.scrollTop property gets or sets the number of pixels by which the content of an element is scrolled vertically. An element's scrollTop value is a measure of the distance from the top of the element's content (rolled up) to its viewport visible content (the top). When an element's content does not produce a vertical scrollbar, its scrollTop value is 0.
The Element.scrollTop property gets or sets the number of pixels by which the content of an element is scrolled vertically. An element's scrollTop value is a measure of the distance from the top of the element's content (rolled up) to its viewport visible content (the top). When an element's content does not produce a vertical scrollbar, its scrollTop value is 0.
This property is read-only and is 0 for elements without CSS or inline layout boxes defined, otherwise, it is the height (in pixels) inside the element, including padding, but excluding horizontal scroll bars, borders, and margins . clientHeight can be calculated by CSS height + CSS padding - horizontal scrollbar height (if present).
This article has been included in the personal blog , welcome to pay attention~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。