本文是深入浅出 ahooks 源码系列文章的第十七篇,该系列已整顿成文档-地址。感觉还不错,给个 star 反对一下哈,Thanks。
简介
useInfiniteScroll 封装了常见的有限滚动逻辑。
具体可看官网
留神:这里的有限滚动指的是常见的点击加载更多或者说下拉加载更加性能,而不是虚构滚动,虚构滚动前面会讲到。
实现原理
实现原理:应用了 useRequest hook 负责进行申请后盾数据。其中 reloadAsync 对应 useRequest 的 runAsync,reload 对应 useRequest 的 run。前者返回 Promise,须要自行处理异样。后者外部曾经做了异样解决。
另外如果传入 target 和 isNoMore 参数,通过监听 scroll 事件,判断是否滚动到指定的地位(反对设置 threshold 值-间隔底部间隔阈值),进行主动发动加载更多申请,从而实现滚动主动加载成果。
大略说完原理,来看代码。
具体实现
入参以及状态定义,能够间接看正文:
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); // 省略代码...};
判断是否有数据:isNoMore 的入参是以后聚合后的 data。
// 判断是否还有数据const noMore = useMemo(() => { if (!isNoMore) return false; return isNoMore(finalData);}, [finalData]);
通过 useRequest 解决申请,能够看到 onBefore、onSuccess、onError、onFinally、manual 等参数都是间接传到了 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 和 reload/reloadAsync 别离对应调用的是 useRequest 的 run 和 runAsync 函数。
// 同步加载更多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();
并且当 reloadDeps 依赖发生变化的时候,会触发 reload,进行重置:
useUpdateEffect(() => { run();}, [...reloadDeps]);
最初就是滚动主动加载的逻辑,通过 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 },);
下面提到的三个重要的值 scrollTop,scrollHeight,clientHeight 对应的值别离为以下后果:
scrollTop
Element.scrollTop 属性能够获取或设置一个元素的内容垂直滚动的像素数。一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的间隔的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为 0。
scrollHeight
Element.scrollTop 属性能够获取或设置一个元素的内容垂直滚动的像素数。一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的间隔的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为 0。
clientHeight
这个属性是只读属性,对于没有定义 CSS 或者内联布局盒子的元素为 0,否则,它是元素外部的高度 (单位像素),蕴含内边距,但不包含程度滚动条、边框和外边距。clientHeight 能够通过 CSS height + CSS padding - 程度滚动条高度 (如果存在) 来计算。
本文已收录到集体博客中,欢送关注~