本文是深入浅出 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.scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。

clientHeight

这个属性是只读属性,对于没有定义 CSS 或者内联布局盒子的元素为 0,否则,它是元素内部的高度 (单位像素),包含内边距,但不包括水平滚动条、边框和外边距。clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度 (如果存在) 来计算。

本文已收录到个人博客中,欢迎关注~

大家都能看得懂的源码之ahooks useInfiniteScroll的更多相关文章

  1. 大家都能看得懂的源码之 ahooks useVirtualList 封装虚拟滚动列表

    本文是深入浅出 ahooks 源码系列文章的第十八篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 简介 提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时 ...

  2. 大家都能看得懂的源码 - ahooks useSet 和 useMap

    本文是深入浅出 ahooks 源码系列文章的第十篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天我们来聊聊 ahooks 中对 Map 和 Set 类型进行状 ...

  3. 大家都能看得懂的源码(一)ahooks 整体架构篇

    本文是深入浅出 ahooks 源码系列文章的第一篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 第一篇主要介绍 ahooks 的背景以及整体架构. React h ...

  4. 大家都能看得懂的源码 - 如何封装 cookie/localStorage/sessionStorage hook?

    本文是深入浅出 ahooks 源码系列文章的第九篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天来看看 ahooks 是怎么封装 cookie/localSt ...

  5. 大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?

    本文是深入浅出 ahooks 源码系列文章的第十三篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用 ...

  6. 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(一)

    本文是深入浅出 ahooks 源码系列文章的第十四篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 上一篇我们探讨了 ahooks 对 DOM 类 Hooks 使用 ...

  7. 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(二)

    本文是深入浅出 ahooks 源码系列文章的第十五篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇接着针对关于 DOM 的各个 Hook 封装进行解读. us ...

  8. 如何读懂Framework源码?如何从应用深入到Framework?

    如何读懂Framework源码? 首先,我也是一个应用层开发者,我想大部分有"如何读懂Framework源码?"这个疑问的,应该大都是应用层开发. 那对于我们来讲,读源码最大的问题 ...

  9. <转>如何高效快速看懂Android源码

    原网址:http://jingyan.baidu.com/article/574c5219ca78ed6c8d9dc12a.html 在Android系统上工作了一段时间,经常会遇到题目中的问题,下面 ...

随机推荐

  1. RPA应用场景-定点取数

    场景概述定点取数 所涉系统名称业务系统,Excel 人工操作(时间/次) 8 小时 所涉人工数量 2 操作频率实时 场景流程 1.从业务系统中拉取指定字段值的数据填入Excel: 2.将Excel每隔 ...

  2. Linux 文件权限相关知识

    文件权限说明 Linux中的文件能否被访问和工具(程序)无关,和访问的用户身份有关(谁去运行这个程序) 进程的发起者(谁去运行这个程序). 进程的发起者若是文件的所有者: 拥有文件的属主权限 进程的发 ...

  3. 宝塔Linux面板安装教程

    宝塔Linux面板安装教程 安装要求: 内存:512M以上,推荐768M以上(纯面板约占系统60M内存) 硬盘:300M以上可用硬盘空间(纯面板约占20M磁盘空间) 系统:CentOS 7.1+ (U ...

  4. JAVA中简单的for循环竟有这么多坑,你踩过吗

    JAVA中简单的for循环竟有这么多坑,你踩过吗 实际的业务项目开发中,大家应该对从给定的list中剔除不满足条件的元素这个操作不陌生吧? 很多同学可以立刻想出很多种实现的方式,但你想到的这些实现方式 ...

  5. AspectJ和AOP细节

    AspectJ1. 简介 AspectJ:Java社区里最完整最流行的AOP框架.(在Spring中AOP是一种思想,而AspectJ是一种AOP的更明确具体实现) 在Spring2.0以上版本中,可 ...

  6. 正睿七连测 DAY5 T2

    题是水题,也不难想,本来是想打暴力先过个小数据, 本来就想再搞搞优化试试能不能过,毕竟这个题理论上 O( $n^2$ ) 是能过的 题干 主要是觉得这个优化很有可取之处,本来超时,一加这个优化就好很多 ...

  7. 记录一次ubuntu安装mysql,远程无法登录问题的解决历程

    进入ubuntu的mysql配置文件 sudo vim debian.cnf [client] host = localhost user = debian-sys-maint password = ...

  8. BufferedImage类

    BufferedImage类(BufferedImage,是一个带缓冲区图像类,主要作用是将一副图片加载到内存中) BufferedImage类 是lmage的一个子类,BufferedImage 生 ...

  9. Java双重校验单例模式详解

    单例模式双重检测java实现: public class Singleton { private volatile static Singleton instance = null; //#1 pub ...

  10. idea 错误: 找不到或无法加载主类 xx.xxx.Application

    原因module 里面缺少iml文件 生成iml文件方法二:(建议):刷新一下Maven Project就会自动生成.iml文件.点击下图红框标记的按钮即可 完成后就会自动生成.iml文件. 方法二: ...