useabortcontroller钩子专注信号管理与自动清理,不泛型化abort方法,而是通过getsignal()提供类型安全的abortsignal,配合泛型请求函数(如get())实现全程类型推导和请求取消。

在 Vue 3 + TypeScript 项目中,编写一个类型安全、可复用的请求取消钩子(如 useAbortController),关键不是“泛型取消”,而是让 AbortController 的创建、绑定、清理和类型提示都与请求函数自然协同。真正的泛型体现在请求返回值类型(比如 T)和信号监听逻辑的复用上,而不是给 abort() 做泛型。
核心思路:信号与请求分离,类型随请求走
不要试图给 abort 方法加泛型,而是把 AbortSignal 注入到具体请求函数中,并确保该请求函数本身是泛型的(如 get<t>()</t>)。钩子只负责生命周期管理 + 类型友好的 signal 提供。
-
钩子不执行请求,只提供
signal和abort方法 -
请求函数(如
get<userlistresult>()</userlistresult>)自带泛型,返回类型自动推导 -
钩子返回的
abort是无参函数,无需泛型 —— 它只是触发标准中止
实现一个类型安全的 useAbortController
下面是一个生产可用的 TypeScript 钩子,支持组件卸载自动清理,并导出可手动调用的 abort:
import { ref, onBeforeUnmount } from 'vue'
export function useAbortController() {
const controller = ref<abortcontroller null>(null)
const abort = () => {
if (controller.value) {
controller.value.abort()
controller.value = null
}
}
// 创建新控制器并返回 signal
const getSignal = (): AbortSignal => {
if (controller.value) abort()
controller.value = new AbortController()
return controller.value.signal
}
// 组件卸载时自动清理
onBeforeUnmount(abort)
return {
abort,
getSignal,
// 可选:暴露 controller 实例用于高级控制(如 reason)
controller: controller as Readonly<ref null>>
}
}</ref></abortcontroller>
注意:getSignal() 每次调用都会新建控制器并自动取消旧的,天然避免重复请求残留。
在组件中配合泛型请求函数使用
结合你已有的泛型 get<t>()</t>(来自 request.ts),这样用即可获得完整类型推导和取消能力:
<script setup lang="ts">
import { useAbortController } from '@/hooks/useAbortController'
import { get } from '@/utils/request'
import type { UserListResult } from '@/types/user'
const { getSignal } = useAbortController()
const fetchUsers = async () => {
try {
// ✅ 类型自动为 Promise<ApiResponse<UserListResult>>
const res = await get<UserListResult>('/api/users', {
params: { page: 1, size: 10 }
}, {
signal: getSignal() // ✅ signal 类型为 AbortSignal,TS 严格校验
})
console.log(res.data.list) // ✅ list 自动补全,类型精准
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求已被取消')
}
}
}
</script>
这里没有额外泛型参数传给钩子,但整个链路——从 getSignal() 到 get<t>()</t> 再到 res.data ——全程类型不丢失。
进阶:封装带取消能力的 useRequest(可选)
如果想进一步封装成类似 React Query 的 useRequest<t>()</t>,可以这样设计返回值类型:
interface UseRequestReturn<t> {
data: Ref<t null>
error: Ref<error null>
isLoading: Ref<boolean>
execute: (options?: { signal?: AbortSignal }) => Promise<void>
abort: () => void
}
export function useRequest<t>(fn: (config: { signal?: AbortSignal }) => Promise<t>): UseRequestReturn<t> {
// 内部用 useAbortController 管理 signal
const { getSignal, abort } = useAbortController()
const data = ref<t null>(null)
const error = ref<error null>(null)
const isLoading = ref(false)
const execute = async (options: { signal?: AbortSignal } = {}) => {
isLoading.value = true
error.value = null
try {
const signal = options.signal ?? getSignal()
data.value = await fn({ signal })
} catch (e) {
if (e instanceof Error && e.name === 'AbortError') return
error.value = e as Error
} finally {
isLoading.value = false
}
}
return { data, error, isLoading, execute, abort }
}</error></t></t></t></t></void></boolean></error></t></t>
调用时直接传入泛型 useRequest<userlistresult>(() => get('/api/users'))</userlistresult>,返回值中 data 类型即为 Ref<userlistresult null></userlistresult>,零配置类型安全。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











