写的可能不够好,希望大佬指点 接受以下参数:
-
service:一个异步函数,用于获取数据。它接受一个布尔值参数isReset,用于指示是否重置数据。该函数返回一个 Promise,用于表示异步操作的结果。 -
container(可选):一个 HTMLDivElement 元素,表示滚动元素的容器。如果不传递此参数,默认使用body元素作为滚动容器。 -
options(可选):一个包含一些配置选项的对象。threshold(可选):一个数字,表示当滚动元素距离底部还有多少像素时触发加载,默认值为 0。afterFetching(可选):一个回调函数,在每次获取数据后执行。它接受一个参数data,表示获取的数据。isEnd(可选):一个函数,用于判断是否已经加载完所有数据。它接受一个可选参数allData,表示当前已加载的所有数据,返回一个布尔值。watch(可选):一个用于监视变化的值,当该值发生变化时,会重新加载数据。
该 hook 返回一个包含两个元素的数组:
- 第一个元素是数据
data,表示已加载的数据。初始值为空数组。 - 第二个元素是一个布尔值
isLoading,表示当前是否正在加载数据。初始值为false。
此外,该 hook 还会根据传入的参数进行一些额外的操作:
- 在首次加载时,会调用
wrappedService函数获取数据。 - 监听
watch参数的变化,当其发生变化时,会重新加载数据,并将watchedValue.current更新为最新的watch值。 - 监听滚动事件,并在满足一定条件时触发加载数据的操作。具体条件为:滚动元素距离底部的距离小于等于
threshold,并且isEnd函数返回false,且当前没有正在加载的数据。 - 在组件卸载时,会移除滚动事件的监听。
总结来说,该 hook 实现了一个滚动加载的功能,通过监听滚动事件,并在满足一定条件时触发异步请求数据的操作,从而实现了数据的动态加载。
import lodash from "lodash";
import { useCallback, useEffect, useRef, useState } from "react";
export interface Option<Data> {
threshold?: number;
afterFetching?: (data: Data) => void;
isEnd?: (allData?: Data) => boolean;
watch?: any;
}
// type TData = Record<string, any>[];
/**
* 滚动加载
* @param service 请求函数
* @param container 滚动元素容器,不传默认使用body
* @param options
* @returns
*/
/**
*
* @description 获取的数据是累积添加的
*/
export function useScrollFetch<T extends any[] | undefined>(
service: (isReset: boolean) => Promise<T>,
container?: HTMLDivElement | undefined,
options?: Option<T>,
): [T | undefined, boolean] {
const { threshold, afterFetching, isEnd, watch } = {
threshold: 0,
...options,
};
if (typeof isEnd !== "function") {
throw Error("options.isEnd must be a function");
}
const [isLoading, toggleLoading] = useState<boolean>(false);
const [data, setData] = useState<T>([] as unknown as T);
const watchedValue = useRef<any>(watch);
const isFetching = useRef<boolean>(false);
const initialized = useRef<boolean>(false);
const wrappedService = useCallback(
(reset?: boolean) => {
isFetching.current = true;
toggleLoading(true);
const promise = service(!!reset);
if (!(promise instanceof Promise)) {
console.warn("service must be an async function");
return Promise.resolve(promise);
}
return promise
.then((response) => {
if (typeof afterFetching === "function") {
afterFetching(response);
}
isFetching.current = false;
if (reset) {
setData(response);
} else if (response) {
setData(data?.concat(response) as T);
}
toggleLoading(false);
})
.catch(() => {
toggleLoading(false);
});
},
[afterFetching, data, service],
);
// 第一次加载
useEffect(() => {
if (!initialized.current) {
wrappedService();
initialized.current = true;
}
}, [wrappedService]);
// 监听watch变化,初始时不应该触发加载,有变化后,需要重置数据
useEffect(() => {
if (typeof watch === "undefined" || watch === null) {
return;
}
const shouldReload = Array.isArray(watchedValue.current)
? watchedValue.current.some((item, index) => !lodash?.isEqual(item, watch[index]))
: watchedValue.current !== watch;
if (shouldReload) {
wrappedService(true);
watchedValue.current = watch;
}
}, [watch, wrappedService]);
const handleOnScroll = useCallback(() => {
const shouldFetch =
(!container
? document.documentElement.scrollHeight - window.scrollY - document.body.offsetHeight <= threshold
: container.scrollHeight - container.scrollTop - container.offsetHeight <= threshold) && !isEnd(data);
if (shouldFetch && !isFetching.current) {
wrappedService();
}
}, [container, data, isEnd, threshold, wrappedService]);
// 添加事件监听
useEffect(() => {
(container || document).addEventListener("scroll", handleOnScroll);
return () => {
(container || document).removeEventListener("scroll", handleOnScroll);
};
}, [container, handleOnScroll]);
useEffect(() => {
if (container) {
// NOTE: 需要给container设置overflow:scroll才监听的到scroll
container!.style!.overflow = "scroll";
}
}, [container]);
return [data, isLoading];
}