我觉得还不错,给个star支持下,谢谢。介绍useInfiniteScroll封装了常见的无限滚动逻辑。详情请见官网。注意:这里的无限滚动指的是普通的点击加载更多或者下拉加载更多功能,而不是虚拟滚动,后面会讲到。实现原理实现原理:useRequesthook用于请求后台数据。其中reloadAsync对应useRequest的runAsync,reload对应useRequest的run。前者返回一个Promise,需要自己处理异常。后者在内部做了异常处理。另外,如果传入target和isNoMore参数,通过监听scroll事件,判断是否滚动到指定位置(支持设置阈值-距离底部的距离阈值),自动发起加载更多requests,从而实现滚动自动加载的效果。说完原理,我们来看代码。输入参数和状态定义的具体实现,可以直接看注释:constuseInfiniteScroll=(//requestserviceservice:Service,options:InfiniteScrollOptions={},)=>{const{//父容器,如果存在,滚动到底部时会自动触发loadMore。需要与isNoMore结合使用才能知道何时到达最后一页。target,//是否有最后一页的判断逻辑,入参为当前聚合数据isNoMore,//下拉自动加载,距离底部的距离阈值=100,//改变后会自动触发重新加载reloadDeps=[],//默认false。即服务在初始化时自动执行。//如果设置为true,则需要手动调用reload或reloadAsync触发执行。manual,//服务执行前触发onBefore,//执行后onSuccess,//服务拒绝时触发onError,//服务执行完成时触发onFinally,}=options;//最终数据const[finalData,setFinalData]=useState();//是否加载更多const[loadingMore,setLoadingMore]=useState(false);//省略代码...};判断是否有数据:isNoMore的入参为当前聚合数据。//判断是否有数据constnoMore=useMemo(()=>{if(!isNoMore)returnfalse;returnisNoMore(finalData);},[finalData]);通过useRequest处理请求,可以看到onBefore、onSuccess、onError、onFinally、manual等参数直接传给了useRequest。//通过useRequest处理请求const{loading,run,runAsync,cancel}=useRequest(//输入参数,将上次请求返回的数据整合到新的参数中async(lastData?:TData)=>{constcurrentData=awaitservice(lastData);//对于第一个请求,直接设置if(!lastData){setFinalData(currentData);}else{setFinalData({...currentData,//服务返回的数据必须包含list数组,类型是{list:any[],...rest}//@ts-ignorelist:[...lastData.list,...currentData.list],});}returncurrentData;},{//手动控制或非手动,//请求结束onFinally:(_,d,e)=>{//设置加载为falsesetLoadingMore(false);onFinally?.(d,e);},//onBefore请求前:()=>onBefore?.(),//请求成功后onSuccess:d=>{setTimeout(()=>{//eslint-disable-next-line@typescript-eslint/no-use-before-definescrollMethod();});onSuccess?.(d);},onError:e=>onError?.(e),},);loadMore/loadMoreAsync和reload/reloadAsync分别调用useRequest的run和runAsync函数//同步加载更多constloadMore=()=>{//如果没有更多,直接返回if(noMore)return;设置加载更多(真);//执行useRequestrun(finalData);};//加载比较异步,返回值为Promise,需要自己处理异常constloadMoreAsync=()=>{if(noMore)returnPromise.reject();设置加载更多(真);返回runAsync(finalData);};constreload=()=>run();constreloadAsync=()=>runAsync();并且当reloadDeps依赖发生变化时,会触发reload重置:useUpdateEffect(()=>{run();},[...reloadDeps]);最后是滚动自动加载的逻辑,通过scrollHeight-scrollTop<=clientHeight+threshold的结果判断是否触底。//滚动方法constscrollMethod=()=>{constel=getTargetElement(target);如果(!el){返回;}//Element.scrollTop属性可以获取或设置元素内容垂直滚动的像素数。constscrollTop=getScrollTop(el);//Element.scrollHeight此只读属性用于衡量元素内容的高度,包括因溢出而在视图中不可见的内容。constscrollHeight=getScrollHeight(el);//该属性只读,对于没有定义CSS或内联布局框的元素为0,否则为元素内部高度(单位像素),包括padding,但不包括水平滚动条、边框、和边距。constclientHeight=getClientHeight(el);//根据以上三个值和阈值判断是否加载更多if(scrollHeight-scrollTop<=clientHeight+threshold){loadMore();}};//监听滚动事件useEventListener('scroll',()=>{if(loading||loadingMore){return;}scrollMethod();},{target},);上面提到的三个重要值scrollTop、scrollHeight、clientHeight对应的值是如下结果:scrollTopElement.scrollTop属性可以获取或设置元素内容垂直滚动的像素数。元素的scrollTop值是从元素内容的顶部(向上滚动)到其视口的可见内容(顶部)的距离的度量。当元素的内容不产生垂直滚动条时,则其scrollTop值为0。scrollHeightElement.scrollTop属性可以获取或设置元素内容垂直滚动的像素数。元素的scrollTop值是从元素内容的顶部(向上滚动)到其视口的可见内容(顶部)的距离的度量。当一个元素的内容不产生垂直滚动条时,那么它的scrollTop值为0。clientHeight是一个只读属性。对于未定义CSS或内联布局框的元素,它为0。否则,它是元素的内部高度(以像素为单位),包括内边距,但不包括水平滚动条、边框和外边缘。距离。clientHeight可以通过CSS高度+CSSpadding-水平滚动条高度(如果存在)来计算。本文已收录在我的个人博客中,欢迎关注~