大家都能看得懂的源码之ahooks useInfiniteScroll
本文是深入浅出 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 对应的值分别为以下结果:
Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为 0。
Element.scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。
这个属性是只读属性,对于没有定义 CSS 或者内联布局盒子的元素为 0,否则,它是元素内部的高度 (单位像素),包含内边距,但不包括水平滚动条、边框和外边距。clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度 (如果存在) 来计算。
本文已收录到个人博客中,欢迎关注~
大家都能看得懂的源码之ahooks useInfiniteScroll的更多相关文章
- 大家都能看得懂的源码之 ahooks useVirtualList 封装虚拟滚动列表
本文是深入浅出 ahooks 源码系列文章的第十八篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 简介 提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时 ...
- 大家都能看得懂的源码 - ahooks useSet 和 useMap
本文是深入浅出 ahooks 源码系列文章的第十篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天我们来聊聊 ahooks 中对 Map 和 Set 类型进行状 ...
- 大家都能看得懂的源码(一)ahooks 整体架构篇
本文是深入浅出 ahooks 源码系列文章的第一篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 第一篇主要介绍 ahooks 的背景以及整体架构. React h ...
- 大家都能看得懂的源码 - 如何封装 cookie/localStorage/sessionStorage hook?
本文是深入浅出 ahooks 源码系列文章的第九篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天来看看 ahooks 是怎么封装 cookie/localSt ...
- 大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?
本文是深入浅出 ahooks 源码系列文章的第十三篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用 ...
- 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(一)
本文是深入浅出 ahooks 源码系列文章的第十四篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 上一篇我们探讨了 ahooks 对 DOM 类 Hooks 使用 ...
- 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(二)
本文是深入浅出 ahooks 源码系列文章的第十五篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇接着针对关于 DOM 的各个 Hook 封装进行解读. us ...
- 如何读懂Framework源码?如何从应用深入到Framework?
如何读懂Framework源码? 首先,我也是一个应用层开发者,我想大部分有"如何读懂Framework源码?"这个疑问的,应该大都是应用层开发. 那对于我们来讲,读源码最大的问题 ...
- <转>如何高效快速看懂Android源码
原网址:http://jingyan.baidu.com/article/574c5219ca78ed6c8d9dc12a.html 在Android系统上工作了一段时间,经常会遇到题目中的问题,下面 ...
随机推荐
- python基础知识-day9(数据驱动)
1.数据驱动的概念 在自动化测试中,需要把测试的数据分离到JSON,YAML等文件中. 2.YAML 的相关知识 YAML 入门教程 分类 编程技术 YAML 是 "YAML Ain't a ...
- SAP 实例 10 List Box with value list from input help
*&---------------------------------------------------------------------* *& Report DEMO_DROP ...
- 解开XAML的邪恶面纱
什么是XAML,首先我们看下它的外观 <Window x:Class="Blend_WPF.WindowStyle" xmlns="http://sc ...
- js 表面学习 - 认识事件
事件 描述 onchange HTML 元素已被改变 onclick 用户点击了 HTML 元素 onmouseover 用户把鼠标移动到 HTML 元素上 onmouseout 用户把鼠标移开 HT ...
- RPA跨系统自动生成采购订单
1.从开发器启动机器人 2.RPA登录友采云 3.RPA根据筛选条件,导出采购订单 4.RPA请并登录NC 5.RPA把读取到的数据,逐个录入到NC系统中,并存储到Excel中 6.RPA将最终的Ex ...
- python小题目练习(三)
题目:输出1!+2!+3!+--+10!的结果代码实现: # 定义一个函数来递归实现阶乘操作def cycle(num): if num == 1: return 1 else: return num ...
- HMS Core音频编辑服务3D音频技术,助力打造沉浸式听觉盛宴
2022年6月28日,HDD·HMS Core.Sparkle影音娱乐沙龙在线上与开发者们见面.HMS Core音频编辑服务(Audio Editor Kit)专家为大家详细分享了基于分离的3D音乐创 ...
- windows脚本bat做文件备份
@ECHO OFF echo 切换到当前目录... cd /d %~dp0% echo 开始复制Code1... echo d | XCOPY Code1 ..\备份\bakdir\Code1 /s ...
- K阶斐波那契数列--------西工大NOJ习题.10
K阶斐波那契数列--------西工大NOJ习题.10 原创不易,转载请说明出处!!! 科普:k阶斐波那契数列的0到n-1项需要有初始值. 其中,0到n-2项初始化为0,第n-1项初始化为1. 在这道 ...
- python shutil相关操作
python shutil模块常用操作 高级的 文件.文件夹.压缩包 处理模块 1.将文件内容拷贝至另一个文件中 shutil.copyfileobj(src, dist, length=16*102 ...