import { onMounted, onScopeDispose, ref, watch, type Ref } from 'vue' import { isCancel } from 'axios' /** fetcher:业务参数在前,AbortSignal 固定作为最后一个参数传入(用于取消请求)。 */ export type RequestFetcher = (...args: [...TParams, AbortSignal]) => Promise export interface UseRequestOptions { /** true 时挂载不自动请求,需手动 run。 */ manual?: boolean /** 首次自动请求 / run() 无参时使用的参数。 */ defaultParams?: TParams /** 响应式依赖,变化时自动用上次参数重新请求。 */ refreshDeps?: unknown[] onBefore?: (params: TParams) => void onSuccess?: (data: TData, params: TParams) => void onError?: (error: unknown, params: TParams) => void onFinally?: (params: TParams) => void } export interface UseRequestResult { data: Ref loading: Ref error: Ref /** 手动触发(成功返回 data,失败/取消返回 undefined)。 */ run: (...args: TParams) => Promise /** 用上一次的参数重新请求。 */ refresh: () => Promise /** 取消当前请求。 */ cancel: () => void /** 本地修改 data(不回发请求)。 */ mutate: (data: TData | ((prev: TData | undefined) => TData)) => void } /** * 数据请求 Hook(类似 ahooks 的 useRequest): * 管理 data/loading/error 状态,支持手动触发、依赖刷新、取消与本地修改。 */ export function useRequest( fetcher: RequestFetcher, options: UseRequestOptions = {}, ): UseRequestResult { const { manual = false, defaultParams = [] as unknown as TParams, refreshDeps } = options const data = ref() const loading = ref(false) const error = ref() let lastParams: TParams = defaultParams let controller: AbortController | null = null let seq = 0 async function run(...args: TParams): Promise { controller?.abort() controller = new AbortController() const mySeq = ++seq lastParams = args.length > 0 ? args : lastParams loading.value = true error.value = undefined options.onBefore?.(lastParams) try { const result = await fetcher(...lastParams, controller.signal) if (mySeq !== seq) return undefined // 已被更新的请求取代 data.value = result options.onSuccess?.(result, lastParams) return result } catch (e) { if (isCanceled(e)) return undefined if (mySeq !== seq) return undefined error.value = e options.onError?.(e, lastParams) return undefined } finally { if (mySeq === seq) loading.value = false options.onFinally?.(lastParams) } } function refresh(): Promise { return run(...lastParams) } function cancel(): void { controller?.abort() } function mutate(next: TData | ((prev: TData | undefined) => TData)): void { data.value = typeof next === 'function' ? (next as (prev: TData | undefined) => TData)(data.value) : next } if (refreshDeps) { watch(refreshDeps, () => { refresh() }) } if (!manual) { onMounted(() => { run(...defaultParams) }) } onScopeDispose(() => { controller?.abort() }) return { data, loading, error, run, refresh, cancel, mutate } } /** 判断是否 axios 主动取消(AbortController)。 */ function isCanceled(e: unknown): boolean { return isCancel(e) }