/* eslint-disable @typescript-eslint/ban-ts-comment */ import { ref, Ref, watch, WatchSource, onUnmounted, computed } from 'vue'; export interface UseRequestReturn { /** 纯净的数据,不包含后端外层壳 */ data: Ref; /** 加载状态 */ loading: Ref; /** 错误对象 */ error: Ref; /** 手动触发请求 */ run: (...args: any[]) => Promise; /** 取消请求 */ cancel: () => void; /** 重置数据 */ reset: () => void; } export function useRequest( fetcher: (...args: any[]) => Promise, options: { /** 是否手动触发,默认 false (即自动触发) */ manual?: boolean; /** 初始数据 */ initialData?: T; /** 依赖数组,变化时重新请求 */ refreshDeps?: WatchSource[]; /** * 数据格式化函数 * 默认逻辑:如果返回是对象且有 data 属性,则返回 res.data,否则返回原数据 */ formatResult?: (res: any) => T; /** 是否准备好可以发起请求,默认 true */ ready?: Ref; } = {}, ): UseRequestReturn & { /** 获取当前 AbortController 的 signal,可传递给 fetcher */ getSignal: () => AbortSignal | undefined; } { const { manual = false, initialData, refreshDeps, formatResult = (res) => (res && typeof res === 'object' && 'data' in res ? res.data : res), ready = ref(true), } = options; const data = ref(initialData); const loading = ref(false); const error = ref(null); let abortController: AbortController | null = null; let isUnmounted = false; onUnmounted(() => { isUnmounted = true; if (abortController) { abortController.abort(); } }); const run = async (...args: any[]): Promise => { if (isUnmounted) return undefined; // 取消之前的请求 if (abortController) { abortController.abort(); } // 创建新的 AbortController abortController = new AbortController(); const currentSignal = abortController.signal; loading.value = true; error.value = null; try { // 将 signal 作为最后一个参数传递给 fetcher // 调用方可以通过最后一个参数获取 signal: async (...args, { signal }) => {...} const res = await fetcher(...args, { signal: currentSignal }); if (currentSignal.aborted || isUnmounted) { return undefined; } const formattedData = formatResult(res); if (!isUnmounted) { data.value = formattedData; } return formattedData; } catch (e: any) { if (e.name === 'AbortError' || e.message?.includes('aborted')) { return undefined; } if (!isUnmounted) { error.value = e; } return undefined; } finally { // 只有当前请求未被取消时才重置 loading if (!currentSignal.aborted && !isUnmounted) { loading.value = false; } // 如果这是当前活动的 controller,清理引用 if (abortController?.signal === currentSignal) { abortController = null; } } }; const cancel = () => { if (abortController) { abortController.abort(); loading.value = false; abortController = null; } }; const reset = () => { cancel(); data.value = initialData; error.value = null; }; // 使用 shouldAutoRun 统一管理自动请求,避免 refreshDeps 和 immediate watch 重复触发 run const shouldAutoRun = computed(() => !manual && ready.value); let hasAutoRun = false; // 标记是否已自动执行过,防止重复触发 watch( shouldAutoRun, (val) => { if (val && !hasAutoRun) { hasAutoRun = true; run(); } else if (!val) { // 当条件不满足时重置 hasAutoRun,使下次满足条件时能再次自动执行 hasAutoRun = false; } }, { immediate: true }, ); // refreshDeps 变化时重新请求(初始化时不再重复触发) if (refreshDeps && refreshDeps.length > 0) { watch( refreshDeps, () => { if (shouldAutoRun.value) { run(); } }, { deep: true }, ); } const getSignal = () => abortController?.signal; return { // @ts-ignore data, loading, error, run, cancel, reset, getSignal, }; }