本文是深入浅出 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. 关于个人项目(臻美MV【仿抖音App】)滑动切换视频的分析(前端角度)

    我们知道你天天刷抖音的时候可以上滑切换视频,互不影响.那么我们站在前端的角度能否可以实现这种效果呢?这是我的个人项目:臻美MV 下面我是用Vue写的,现在我把它开源. Vue: 初始界面 <te ...

  2. oracle备份数据库数据及导入数据库

    1.oracle数据库备份和导入 bat 脚本 scott oracle数据库用户名称 123456 数据库scott用户下的密码 192.168.124.8 本电脑IP orcl 为oracle库 ...

  3. 7.Spark SQL

    1.分析SparkSQL出现的原因,并简述SparkSQL的起源与发展. SparkSQL出现是因为关系数据库已经不能满足各种在大数据时代新增的用户需求.首先,用户需要在不同的结构化和非结构化数据中执 ...

  4. 论文解读(ValidUtil)《Rethinking the Setting of Semi-supervised Learning on Graphs》

    论文信息 论文标题:Rethinking the Setting of Semi-supervised Learning on Graphs论文作者:Ziang Li, Ming Ding, Weik ...

  5. linux服务配置IP或者说可以远程连接

    切换目录 cd /etc/sysconfig/network-scripts ls查看当前目录下的东西 找到ipcfg- 开头的,而且不是iocfg-lo,而上图就是那个ifcfg-ens33. 则进 ...

  6. ORM框架介绍——什么是ORM框架?

    1.什么是ORM?对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.ORM框架是连接数据库的桥梁,只要提供 ...

  7. 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析

    一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...

  8. 精心总结十三条建议,帮你创建更合适的MySQL索引

    上篇文章讲到使用MySQL的Explain命令可以分析SQL性能瓶颈,优化SQL查询,以及查看是否用到了索引. 我们都知道创建索引可以提高查询效率,但是具体该怎么创建索引? 哪些字段适合创建索引? 哪 ...

  9. day13--Java常用类

    Java常用类 1.包装类 1.1什么是包装类? Java 是面向对象的语言,但不是"纯面向对象"的,比如我们经常用到的基本数据类型就不是对象. 在我们实际应用中,经常需要将基本数 ...

  10. 【web自动化测试】playwright安装失败怎么办

    在安装中,如果没有输入 playwright install, 则不会安装浏览器,运行 playwright codegen 时会报浏览器找不到的错误: "chromium" br ...