Vue3 Axios 进阶封装:插件化架构搭建 + AbortController 实现请求取消
在 Vue3 项目开发中,网络请求的封装是基础且核心的工作,一个优雅的请求框架能大幅提升开发效率、降低维护成本。此前我们已经完成了 Vue3 网络请求的基础封装,包括架构设计、Mock 服务搭建、Axios 面向对象封装和拦截器配置,而实际开发中,页面跳转取消未完成请求、搜索框输入取消旧请求、手动终止正在进行的请求等场景十分常见,这就需要为请求框架添加取消能力。
更重要的是,为了让后续的请求防重、重试等功能可扩展、解耦,我们需要先将请求核心层改造为插件化架构,让取消、防重、重试等功能都以插件形式接入,实现真正的模块化开发。本文就带大家一步步完成 Vue3 Axios 的插件化改造,并基于浏览器原生 API AbortController 实现请求取消功能。
一、核心层插件化架构设计
插件化架构的核心是定义统一插件接口+实现插件管理类,所有扩展功能都实现统一接口,由管理类负责注册和应用,这样既能保证规范,又能灵活增删插件。
1.1 定义统一的 HTTP 插件接口
首先在src/http/core/目录下创建plugin.ts,定义所有 HTTP 插件必须实现的接口,仅需包含一个apply方法,用于将插件应用到 Axios 实例上。
importtype { AxiosInstance } from'axios'
/**
* HTTP 插件接口
* 所有网络请求相关功能(取消、防重、重试)都需实现该接口
*/
exportinterface HttpPlugin {
/**
* 应用插件到 Axios 实例
* @param instance Axios 实例
*/
apply(instance: AxiosInstance): void
}
1.2 实现插件管理类
接着在同目录创建plugin-manager.ts,实现插件的注册、批量应用、清空功能,后续所有插件都由这个管理类统一管控,无需手动逐个处理。
importtype { AxiosInstance } from'axios'
importtype { HttpPlugin } from'./plugin.ts'
/**
* 插件管理器
* 负责HTTP插件的注册、应用和清空
*/
exportclass PluginManager {
private plugins: HttpPlugin[] = []
/**
* 注册单个插件
* @param plugin 实现HttpPlugin接口的插件实例
*/
public register(plugin: HttpPlugin): void {
this.plugins.push(plugin)
}
/**
* 将所有注册的插件应用到Axios实例
* @param instance Axios实例
*/
public applyAll(instance: AxiosInstance): void {
this.plugins.forEach(plugin => plugin.apply(instance))
}
/**
* 清空所有已注册插件
*/
public clear(): void {
this.plugins = []
}
}
配图建议:Vue3 HTTP 插件化架构分层图,展示「业务层 → HttpClient 核心 → 插件管理器 → 各类功能插件(取消、防重、重试)→ Axios 实例」的调用关系,突出插件与核心层的解耦。
至此,插件化架构的基础就搭建完成了,接下来所有的功能扩展,都只需实现HttpPlugin接口,再通过PluginManager注册即可,无需修改核心代码,符合开闭原则。
二、基于 AbortController 实现请求取消插件
实现请求取消前,先明确 Axios 的取消方案:Axios v0.22.0 之前使用CancelToken,但该 API 已被弃用,v0.22.0 之后推荐使用浏览器原生 API AbortController(本文使用 Axios 1.13.2)。
AbortController是浏览器原生的异步操作取消 API,核心原理是:创建控制器实例→将实例的signal属性绑定到请求→调用实例的abort()方法,即可触发请求取消,绑定的signal会进入已取消状态。
2.1 实现 RequestCanceler 取消插件
在src/http/core/目录下创建request-canceler.ts,创建RequestCanceler类并实现HttpPlugin接口,这是请求取消的核心代码,核心逻辑是通过Map存储请求唯一标识与AbortController 实例的映射关系,实现请求的添加、移除、单个取消和批量取消。
importtype { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from'axios'
importtype { HttpPlugin } from'./plugin.ts'
/**
* 请求取消器插件
* 实现HttpPlugin接口,负责请求的管理和取消
*/
exportclass RequestCanceler implements HttpPlugin {
// 存储请求标识与取消控制器的映射表
private cancelMap: Map<string, AbortController>
constructor() {
this.cancelMap = new Map()
}
/**
* 生成请求的唯一标识
* 基于请求方式、地址、参数、请求体生成,保证相同请求唯一匹配
* @param config Axios请求配置
*/
private generateKey(config: AxiosRequestConfig): string {
const { url, method, params, data } = config
return`${method || 'GET'}-${url}-${JSON.stringify(params || {})}-${JSON.stringify(data || {})}`
}
/**
* 添加请求到映射表,添加前先取消相同的未完成请求
* @param config Axios请求配置
*/
public add(config: AxiosRequestConfig): void {
this.remove(config) // 先取消相同请求,避免重复请求
const key = this.generateKey(config)
const controller = new AbortController()
config.signal = controller.signal // 将signal绑定到请求配置
this.cancelMap.set(key, controller)
}
/**
* 取消指定请求并从映射表中移除
* @param config Axios请求配置
*/
public remove(config: AxiosRequestConfig): void {
const key = this.generateKey(config)
if (this.cancelMap.has(key)) {
const controller = this.cancelMap.get(key)
controller?.abort() // 触发请求取消
this.cancelMap.delete(key) // 移除映射关系
}
}
/**
* 取消所有未完成的请求并清空映射表
*/
public clear(): void {
this.cancelMap.forEach((controller) => {
controller.abort()
})
this.cancelMap.clear()
}
/**
* 应用插件到Axios实例
* 通过请求/响应拦截器实现请求的自动添加和移除
* @param instance Axios实例
*/
public apply(instance: AxiosInstance): void {
// 请求拦截器:发起请求前,将请求添加到取消器
instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
this.add(config)
return config asany
},
(error: AxiosError) => {
returnPromise.reject(error)
}
)
// 响应拦截器:请求完成(成功/失败)后,从取消器中移除
instance.interceptors.response.use(
(response: AxiosResponse) => {
this.remove(response.config)
return response
},
(error: AxiosError) => {
if (error.config) {
this.remove(error.config)
}
returnPromise.reject(error)
}
)
}
}
核心逻辑梳理:
-
发起请求时,请求拦截器自动调用 add方法,生成唯一标识并绑定signal,将请求加入映射表; -
请求完成(成功 / 失败)时,响应拦截器自动调用 remove方法,取消请求并移除映射; -
手动取消单个 / 所有请求时,调用 remove/clear方法即可; -
添加新请求前先取消相同请求,可直接解决搜索框输入重复请求的问题。
配图建议:AbortController 请求取消工作流程图,展示「创建控制器→绑定 signal→发起请求→调用 abort ()→取消请求→移除映射」的完整流程,标注关键方法和属性。
2.2 将取消插件集成到 HttpClient 核心
插件实现后,需要将其集成到之前的HttpClient类中,并添加开关配置,让项目可以按需开启请求取消功能,步骤分为两步:扩展配置项、改造 HttpClient 类。
步骤 1:扩展 HttpClient 配置接口
在src/http/core/types.ts中,为HttpClientConfig添加enableCancel配置项,用于控制是否开启请求取消:
/**
* HTTP请求客户端配置
*/
exportinterface HttpClientConfig {
baseURL?: string
timeout?: number
headers?: Record<string, string>
interceptor?: InterceptorConfig
enableCancel?: boolean// 新增:是否开启请求取消功能,默认true
}
步骤 2:改造 HttpClient 类集成插件
在src/http/core/http-client.ts中,引入PluginManager和RequestCanceler,通过插件管理器注册取消插件,并添加cancelAll方法供外部调用,同时修正原文笔误(config.cancelable改为config.enableCancel):
// 导入插件相关模块
import { RequestCanceler } from'./request-canceler.ts'
import { PluginManager } from'./plugin-manager.ts'
importtype { HttpClientConfig } from'./types.ts'
import axios from'axios'
// 默认配置,开启请求取消
const defaultConfig: HttpClientConfig = {
timeout: 5000,
enableCancel: true,
}
exportclass HttpClient {
public instance: axios.AxiosInstance
public config: HttpClientConfig
private pluginManager: PluginManager // 插件管理器实例
private requestCanceler: RequestCanceler // 请求取消插件实例
constructor(config: HttpClientConfig = {}) {
// 合并配置
this.config = { ...defaultConfig, ...config }
// 创建Axios实例
this.instance = axios.create(this.config)
// 初始化插件管理器和取消插件
this.pluginManager = new PluginManager()
this.requestCanceler = new RequestCanceler()
// 注册并应用插件
this.registerPlugins()
// 初始化拦截器(原有逻辑)
this.setInterceptors()
}
/**
* 注册插件:根据配置决定是否开启请求取消
*/
private registerPlugins() {
if (this.config.enableCancel) {
this.pluginManager.register(this.requestCanceler)
}
// 将所有注册的插件应用到Axios实例
this.pluginManager.applyAll(this.instance)
}
/**
* 取消所有未完成的请求(供外部调用)
*/
public cancelAll(): void {
this.requestCanceler.clear()
}
/**
* 获取插件管理器,支持动态添加/移除插件
*/
public getPluginManager(): PluginManager {
returnthis.pluginManager
}
// 原有拦截器方法setInterceptors...
}
至此,请求取消插件就完全集成到了请求核心层,项目中创建的api实例会自动拥有请求取消能力,且可通过enableCancel: false关闭该功能。
2.3 测试请求取消功能
为了验证功能是否生效,我们在src/pages/http-demo.vue中添加测试按钮,实现发送多个请求后,1 秒后批量取消所有请求的功能,实际开发中可将cancelAll方法绑定到页面跳转、组件卸载等钩子中。
<template>
<divclass="http-demo">
<!-- 原有请求测试内容 -->
<divclass="test-btn-group"style="margin-top: 20px;">
<button @click="onTestCancelAllRequests"style="margin-left: 10px;">
测试取消全部请求
</button>
</div>
</div>
</template>
<scriptsetuplang="ts">
import { api } from'@/http'// 项目中创建的HttpClient实例
// 原有请求方法fetchData...
const fetchData = async (params) => {
try {
await api.get('/api/list', { params })
console.log('请求成功:', params)
} catch (err) {
console.log('请求结果:', err.message)
}
}
// 测试取消所有请求
const onTestCancelAllRequests = async () => {
console.log('开始发送多个请求')
// 连续发送3个分页请求
for (let i = 0; i < 3; i++) {
fetchData({ pageNum: i + 1 })
}
// 1秒后取消所有未完成的请求
setTimeout(() => {
console.log('执行取消所有请求操作')
api.cancelAll()
}, 1000)
}
</script>
测试效果:启动项目后点击测试按钮,打开浏览器 F12 网络面板,会看到 3 个请求被发起,1 秒后所有请求的状态变为canceled,控制台输出「执行取消所有请求操作」,说明请求取消功能生效。
配图建议:浏览器网络面板测试截图,标注请求的canceled状态,以及控制台的日志输出,直观展示取消效果。
三、本文小结
本文完成了 Vue3 Axios 网络请求框架的两大核心改造:
-
搭建了插件化架构:通过定义统一的 HttpPlugin接口和PluginManager管理类,让后续的请求防重、重试等功能都能以插件形式解耦接入,遵循模块化和可扩展原则; -
实现了请求取消插件:基于浏览器原生 AbortController替代废弃的CancelToken,通过拦截器实现请求的自动添加 / 移除,支持单个请求取消和批量取消,解决了实际开发中的多种取消场景。
本次改造后,请求核心层的扩展性大幅提升,下一篇我们将基于这套插件化架构,继续实现请求防重插件,解决项目中重复点击提交按钮导致的重复请求问题,敬请期待!
如果大家在 Axios 封装或插件化开发中有任何问题,欢迎在评论区留言交流,一起探讨更优雅的前端开发方案~
夜雨聆风
