/** * 请求 Hook * 提供统一的请求状态管理 */ import { useState, useCallback, useRef, useEffect } from 'react'; import { AxiosError } from 'axios'; import type { RequestConfig } from '@/src/utils/network/api'; /** * 请求状态 */ export interface RequestState { data: T | null; loading: boolean; error: Error | null; } /** * 请求选项 */ export interface UseRequestOptions 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( requestFn: () => Promise, options: UseRequestOptions = {} ) { const { immediate = false, onSuccess, onError, onFinally, defaultData = null } = options; const [state, setState] = useState>({ data: defaultData, loading: false, error: null, }); const requestRef = useRef(requestFn); requestRef.current = requestFn; const abortControllerRef = useRef(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( 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, }; }