页面初始化以及搜索参数变化时,假设调用fetchData方法请求数据;
(假设 filterValA、filterValB 是搜索条件)
const DEFAULT_CURRENT_PAGE = 1
const [total, setTotal] = useState(0); // 数据总数
const [itemList, setItemList] = useState<itemType[]>([]); // 列表数据
const [currentPage, setCurrentPage] = useState(DEFAULT_CURRENT_PAGE); // 当前页数
useEffect(() => {
fetchData({ current: DEFAULT_CURRENT_PAGE, reset: true });
}, [filterValA, filterValB]);
fetchData方法 :包含触底滚动逻辑
const fetchData = useRefCallback(
async (params: { current?: number; reset?: boolean }) => {
// 发起xhr请求
const res = await getSomeData(someParams)
// 存储数据总条目
setTotal(res.total);
// 处理数据
// 如果是触发滚动加载,则在现有数据后接上新数据;
// 如果是搜索后页面更新,则用新数据替换现有数据
const data = res.data_list || [];
const newData = params.reset ? data : itemList.concat(data);
setItemList(newData);
// 获取这批数据最后一个项目的id
const lastItemId = newData.concat([]).pop()?.id;
// 重点:无限滚动 observer 逻辑
setTimeout(() => { // 为了防止渲染未完成dom上没有新节点,延时500ms执行
if (!scrollObserver) {
// 首次初始化逻辑: 新建一个observer,定义好callback(在下方代码块)
const observer = new IntersectionObserver(
entries => callback(entries, observer),
{ threshold: [0, 1] },
);
// 将item id作为dom节点的ID,监听最后一个item
const newTarget = document.querySelector(`#${lastItemId}`);
newTarget && observer.observe(newTarget);
// 保存observer对象
setScrollObserver(observer);
} else {
// 二次及以后逻辑
// 将item id作为dom节点的ID,监听最后一个item
const newTarget = document.querySelector(`#${lastItemId}`);
newTarget && scrollObserver.observe(newTarget);
}
}, 500);
},
[],
);
observer监听对象视野进入阈值事件回调
const callback = useRefCallback(
(entries: IntersectionObserverEntry[], observer) => {
const entry = entries[0]; // 一次只监听一个,所以可以直接取[0]
if (entry?.intersectionRatio > 0) {
// 元素出现后,获取dom ID,解除对这个元素的监听
const target = document.querySelector(`#${entry?.target.id}`);
target && observer.unobserve(target);
// 判断是否最后一页,否则调用fetchList拉取下一页数据并进行新元素的监听
const isFinish =
spoCount && currentPage >= Math.ceil(spoCount / DEFAULT_PAGE_SIZE);
if (isFinish) return;
fetchList({ current: currentPage + 1 });
setCurrentPage(currentPage => currentPage + 1);
}
},
[],
);
使用自定义的useRefCallback公用方法,保证函数里取到最新的变量。
(useRefCallback原理另起文章详解)
import { useCallback, useRef } from 'react';
export const useRefCallback = <T extends (...args: any[]) => any>(
fn: T,
_?: React.DependencyList,
) => {
const ref = useRef(fn);
ref.current = fn;
return useCallback((...args: any[]) => {
const fn = ref.current;
return fn(...args);
}, []) as T;
};