feat: update
This commit is contained in:
291
src/hooks/useRequest.ts
Normal file
291
src/hooks/useRequest.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 请求 Hook
|
||||
* 提供统一的请求状态管理
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { AxiosError } from 'axios';
|
||||
import type { RequestConfig } from '@/src/utils/network/api';
|
||||
|
||||
/**
|
||||
* 请求状态
|
||||
*/
|
||||
export interface RequestState<T> {
|
||||
data: T | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求选项
|
||||
*/
|
||||
export interface UseRequestOptions<T> extends RequestConfig {
|
||||
/** 是否立即执行 */
|
||||
immediate?: boolean;
|
||||
/** 成功回调 */
|
||||
onSuccess?: (data: T) => void;
|
||||
/** 失败回调 */
|
||||
onError?: (error: Error) => void;
|
||||
/** 完成回调(无论成功失败) */
|
||||
onFinally?: () => void;
|
||||
/** 默认数据 */
|
||||
defaultData?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 Hook
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data, loading, error, run, refresh } = useRequest(
|
||||
* () => request.get('/api/users'),
|
||||
* { immediate: true }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function useRequest<T = any>(
|
||||
requestFn: () => Promise<T>,
|
||||
options: UseRequestOptions<T> = {}
|
||||
) {
|
||||
const { immediate = false, onSuccess, onError, onFinally, defaultData = null } = options;
|
||||
|
||||
const [state, setState] = useState<RequestState<T>>({
|
||||
data: defaultData,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const requestRef = useRef(requestFn);
|
||||
requestRef.current = requestFn;
|
||||
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
/**
|
||||
* 执行请求
|
||||
*/
|
||||
const run = useCallback(
|
||||
async (...args: any[]) => {
|
||||
// 取消之前的请求
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
|
||||
// 创建新的 AbortController
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: true,
|
||||
error: null,
|
||||
}));
|
||||
|
||||
try {
|
||||
const data = await requestRef.current();
|
||||
|
||||
setState({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
onSuccess?.(data);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: err,
|
||||
}));
|
||||
|
||||
onError?.(err);
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
onFinally?.();
|
||||
}
|
||||
},
|
||||
[onSuccess, onError, onFinally]
|
||||
);
|
||||
|
||||
/**
|
||||
* 刷新(重新执行请求)
|
||||
*/
|
||||
const refresh = useCallback(() => {
|
||||
return run();
|
||||
}, [run]);
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
setState({
|
||||
data: defaultData,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
}, [defaultData]);
|
||||
|
||||
/**
|
||||
* 取消请求
|
||||
*/
|
||||
const cancel = useCallback(() => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 立即执行
|
||||
useEffect(() => {
|
||||
if (immediate) {
|
||||
run();
|
||||
}
|
||||
|
||||
// 组件卸载时取消请求
|
||||
return () => {
|
||||
cancel();
|
||||
};
|
||||
}, [immediate]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return {
|
||||
...state,
|
||||
run,
|
||||
refresh,
|
||||
reset,
|
||||
cancel,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页请求 Hook
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data, loading, loadMore, refresh, hasMore } = usePagination(
|
||||
* (page, pageSize) => request.get('/api/users', { params: { page, pageSize } })
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function usePagination<T = any>(
|
||||
requestFn: (
|
||||
page: number,
|
||||
pageSize: number
|
||||
) => Promise<{
|
||||
list: T[];
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
}>,
|
||||
options: {
|
||||
pageSize?: number;
|
||||
immediate?: boolean;
|
||||
onSuccess?: (data: T[]) => void;
|
||||
onError?: (error: Error) => void;
|
||||
} = {}
|
||||
) {
|
||||
const { pageSize = 20, immediate = false, onSuccess, onError } = options;
|
||||
|
||||
const [state, setState] = useState({
|
||||
data: [] as T[],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null as Error | null,
|
||||
page: 1,
|
||||
total: 0,
|
||||
hasMore: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const load = useCallback(
|
||||
async (page: number, append = false) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: !append,
|
||||
loadingMore: append,
|
||||
error: null,
|
||||
}));
|
||||
|
||||
try {
|
||||
const result = await requestFn(page, pageSize);
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
data: append ? [...prev.data, ...result.list] : result.list,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
page,
|
||||
total: result.total,
|
||||
hasMore: result.hasMore,
|
||||
}));
|
||||
|
||||
onSuccess?.(result.list);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: err,
|
||||
}));
|
||||
|
||||
onError?.(err);
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[requestFn, pageSize, onSuccess, onError]
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载更多
|
||||
*/
|
||||
const loadMore = useCallback(async () => {
|
||||
if (state.loadingMore || !state.hasMore) {
|
||||
return;
|
||||
}
|
||||
|
||||
return load(state.page + 1, true);
|
||||
}, [state.loadingMore, state.hasMore, state.page, load]);
|
||||
|
||||
/**
|
||||
* 刷新(重新加载第一页)
|
||||
*/
|
||||
const refresh = useCallback(async () => {
|
||||
return load(1, false);
|
||||
}, [load]);
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
setState({
|
||||
data: [],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
page: 1,
|
||||
total: 0,
|
||||
hasMore: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 立即执行
|
||||
useEffect(() => {
|
||||
if (immediate) {
|
||||
load(1, false);
|
||||
}
|
||||
}, [immediate]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return {
|
||||
...state,
|
||||
loadMore,
|
||||
refresh,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user