无限滚动在前端开发中很常见,为了图省事本人在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>)