大家都能看得懂的源码之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系统上工作了一段时间,经常会遇到题目中的问题,下面 ...
随机推荐
- Kolla部署Pike版本的OpenStack-allinone云平台
1 openstack 概述 openstack概述 : OpenStack是一个NASA美国国家航空航天局和Rackspace合作研发 的,以Apache许可证授权,并且是一个自由软件和开放源代码项 ...
- Ubuntu系统iptables安全防护整改计划
端口开放 默认防火墙是开放所有端口的,如果拿来做应用服务器,就很危险,所以要把防火墙用起来,只将需要的端口开放,ubuntu用的是iptables防火墙. iptables处理流程 iptables ...
- jieba分词的功能和性能分析
jieba分词问题导引 用户词典大小最大可以有多大 用户词典大小对速度的影响 有相同前缀和后缀的词汇如何区分 对比百度分词的API 问题一:词典大小 从源码大小分析,整个jieba分词的源码总容量为8 ...
- 深入解析kubernetes中的选举机制
Overview 在 Kubernetes的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumt ...
- Linux YUM制作自己的yum repository
Linux YUM制作自己的yum repository 配置步骤: 1.通过网络发布自己的package目录 2.创建本地repository 3.配置自己的yum源 操作实现: 1 安装creat ...
- SLSA 框架与软件供应链安全防护
随着软件供应链攻击浪潮愈演愈烈,Google 发布了一系列指南来确保软件包的完整性,旨在防止影响软件供应链的未经授权的代码修改.新的 Google SLSA 框架(Supply-chain Level ...
- SQLZOO练习(一)SELECT BASICS,SELECT form world
name continent area population gdp Afghanistan Asia 652230 25500100 20343000000 Albania Europe 28748 ...
- Python List 中的append 和 extend 的区别
方法的参数不同 append 方法是向原list的末尾添加一个对象(任意对象:如元组,字典,列表等),且只占据一个原list的索引位,添加后无返回值,直接在原列表中添加. list.append(ob ...
- 2022.7.19 $\mathcal{S\,\,T\,L}$
\(STL\) 容器的共有特征:.begin() .end()//表示最后一个元素的后面那个位置 .size()//无符号整型 .swap(b) ::iterator//迭代器 关于迭代器 迭代器可以 ...
- php和js的不定参
function my_func() { $args = func_get_args(); print_r($args); } my_func('php','java','node.js'); jav ...