相比于https://github.com/vercel/swr 我们常用自己的封装的api进行请求,不需要绑定具体的网络框架
TypeScript
///滚动加载demo
const [pageNum, setPageNum] = useState(1);
const [notificationList, setNotificationList] = useState<NotificationItemDTO[]>([]);
/// 真实请求数据
const asyncResult = useAsync(async () => {
const value = await notificationService
.getNotificationList(pageNum, 50)
.bindErrorNotice()
setNotificationList(prev => pageNum === 1 ? value.list : [...prev, ...value.list]);
return value;
}, [pageNum]); // pageNum 变化时重新执行
/// 处理加载更多
const handleLoadMore = useHandleLoadMore(asyncResult, setPageNum)
实现:用react-async-hook进行二次封装
TypeScript
import {useCallback, useEffect, useRef} from "react";
import {ApiPageDataFieldDTO} from "@/lib/models/ApiResponse";
import {hasNextPage} from "@/lib/models/PaginationDTO";
/**
* 返回一个稳定的回调(empty deps)。
* 适合不想因 asyncResult 变化频繁重建回调的场景。
*
* 用法:
* const handleLoadMore = useStableHandleLoadMore(asyncResult, setPageNum);
* // 自定义是否有下一页的判定逻辑
* // const handleLoadMore = useHandleLoadMoreCustom<TemplatePaginationResponse, UseAsyncReturn<TemplatePaginationResponse>>(asyncResult, setPageNum, (data) => hasNextPage(data?.pagination))
*
* 在对应的组件上使用
* endReached={handleLoadMore}
* atBottomStateChange={(atBottom) => {
* if (atBottom) {
* handleLoadMore()
* }
* }}
*/
export function useHandleLoadMore<TData extends ApiPageDataFieldDTO<any>, TAsyncResult extends {
loading: boolean;
error: unknown;
execute: () => Promise<TData>;
result?: TData
}>(
asyncResult: TAsyncResult,
setPageNum: (updater: (p: number) => number) => void,
) {
return useHandleLoadMoreCustom<ApiPageDataFieldDTO<any>, TAsyncResult>(
asyncResult,
setPageNum,
(data) => hasNextPage(data?.pagination)
);
}
/**
* 可自定义 hasNextPage 判定的版本(名称:useHandleLoadMoreCustom)
* - hasNextPageFn 接受完整的 TData(可能为 undefined),返回 boolean
* - 返回稳定回调,内部通过 ref 读取最新 asyncResult 与 hasNextPageFn
*/
export function useHandleLoadMoreCustom<TData, TAsyncResult extends {
loading: boolean;
error: unknown;
execute?: () => Promise<TData>;
result?: TData;
}>(
asyncResult: TAsyncResult,
setPageNum: (updater: (p: number) => number) => void,
hasNextPage: (data: TData | undefined) => boolean,
): () => void {
const ref = useRef<TAsyncResult>(asyncResult);
useEffect(() => {
ref.current = asyncResult;
}, [asyncResult]);
// 保证读取到最新的判断函数,同时回调保持稳定(不随函数重建)
const hasNextRef = useRef<(data: TData | undefined) => boolean>(
(d) => hasNextPage((d as any)?.pagination)
);
useEffect(() => {
hasNextRef.current = hasNextPage;
}, [hasNextPage]);
return useCallback(() => {
const cur = ref.current;
if (!cur) return;
if (cur.loading) return;
if (cur.error) {
cur.execute?.();
return;
}
// 使用最新的 hasNext 判定
if (!hasNextRef.current(cur.result)) return;
///进行下一页
setPageNum(p => p + 1);
}, [setPageNum]);
}
其他类
TypeScript
// API 响应接口
import {PaginationDTO} from "@/lib/models/PaginationDTO";
/**
* 根组件API响应格式
*/
export interface ApiResponse<T> {
code: number
message: string
data: T
}
/**
* 分页数据字段接口
*/
export interface ApiPageDataFieldDTO<T> {
list: T[]
pagination: PaginationDTO
}
export const ApiResponseUtils = {
/**
* 获取BODY中的data field字段,如果不是成功状态将抛出异常
* @param body
*/
getDataFieldOrThrow<T>(body: ApiResponse<T>) {
const code = body.code
if (code !== 0 && code !== 200) {
///格式不要动
throw new Error(`API Error: (${code})${body.message}`)
}
return body.data
}
}
ui组件进行绑定
TypeScript
<Virtuoso className="w-full flex-1 min-h-[50vh] max-h-[70vh]"
data={notificationList}
itemContent={(index, notification) => {
return (<div key={notification.id}
onClick={() => {
clearNotification(notification.id)
}}>
<NotificationCard
key={notification.id}
notification={notification}
isFirst={index === 0}
/>
</div>)
}}
endReached={handleLoadMore}
atBottomStateChange={(atBottom) => {
if (atBottom) {
handleLoadMore()
}
}}
overscan={200}
components={{
Footer: () => {
return (<Refresh.Footer hasMore={hasNextPage(asyncResult.result?.pagination)}
isLoading={asyncResult.loading}
error={asyncResult.error}/>)
}
}}
/>