react+typescript实现无限滚动功能
无限滚动在前端开发中很常见,为了图省事本人在github上尝试了一些开源的无限滚动组件,发现都不好用,而且都许久没有更新了,拿我尝试使用的两个组件【react-infinite-scroll-component,react-infinite-scroller】为例说明一下使用过程中遇到的坑:
1 滚动到底后,会进行无限的请求。
这是由于请求后插入新的元素时,滚动条一直保持在底部,导致无限触发请求。
2. 列表在弹出框(Modal)中时,recentpage无法重置。
进行关闭弹框,搜索列表操作等,next回调参数page仍然为原page,但此时逻辑应该需要重置为1。
基于以上无法解决的问题,我自己实现了一个无限滚动的组件,具体代码入下:
import React, { useEffect, useRef, useState } from 'react'; interface Props { children: React.ReactNode; // 指定父元素dom,默认为直接父元素 scrollableTarget?: HTMLElement | null; // 当前页,用于确认下一页,必填 recentPage: number; // 加载提示 loader?: React.ReactNode; // 是否有下一个 hasMore: boolean; // 指定加载提示元素的高度 moreHeight?: number; next: (page: number) => void; } let preHeight = 0; const SimpleScroll = (props: Props) => { const [parentNode, setparentNode] = useState<HTMLElement>(); const ref = useRef<HTMLDivElement>(null); useEffect(() => { // 初始化父节点 const node = props.scrollableTarget ? props.scrollableTarget : (ref.current?.parentNode as HTMLElement); setparentNode(node); }, []); useEffect(() => { // 重置位置 if (parentNode && props.recentPage && props.recentPage === 1) { parentNode.scrollTop = 0; } if (parentNode && props.recentPage && props.hasMore) { const listener = (e: any) => { if (e && e.target) { console.log(e.target.scrollTop, e.target.clientHeight, e.target.scrollHeight); // 高度突变判断,解决请求后高度变化导致一直请求的bug if (preHeight !== e.target.scrollHeight) { if (parentNode) { parentNode.scrollTop = preHeight; } preHeight = e.target.scrollHeight; return; } preHeight = e.target.scrollHeight; if ( e.target.scrollTop + e.target.clientHeight + (props.moreHeight || 0) >= e.target.scrollHeight ) { props.next(props.recentPage + 1); } } }; parentNode.addEventListener('scroll', listener); return () => { parentNode.removeEventListener('scroll', listener); }; } }, [parentNode, props.scrollableTarget, props.recentPage, props.hasMore]); return ( <div ref={ref}> {props.children} {props.hasMore && props.loader} </div> ); }; export default SimpleScroll;复制代码
将recentPage作为参数传入,子组件知道当前状态,解决了问题1,通过对上一次的dom高度preHeight的判断来区别是否为插入新元素,解决了问题2。
以下为使用方法:
useEffect(() => { // 出发请求 if (query) { getList(query); } }, [query]); const getList = (params?: InFireQuery) => { ... //列表请求 }; return ( <SimpleScroll next={val => setQuery(pre => ({ ...pre, page: val }))} recentPage={fireList.page} loader={<div>加载中</div>} hasMore={hasMore}> .... // 遍历元素生成列表 </SimpleScroll>)
作者:voidJay
链接:https://juejin.cn/post/7021796590214430756