Vue.js 3 - 封装Axios+TypeScript网络请求类教程(拦截器、类型定义、Loading)
作者:hangge | 2026-10-29 09:42
在实际开发中,我们会对网络请求进行二次封装,以提高代码的可复用性和可维护性。通过封装 axios,我们可以统一处理所有的请求、响应及异常,避免在每个组件中重复编写相同的逻辑。 本文将详细介绍如何在 Vue3 + TypeScript 项目中对 axios 进行封装,包括创建请求类、添加拦截器、类型定义以及 Loading 加载效果等。



(2)这样使用该实例的请求都会自动显示 Loading 效果。

(2)底层请求通用类型(service/request/type.ts)
(3)核心请求类封装(service/request/index.ts)
(4)统一导出请求实例、所有业务 API,供全局引入(service/index.ts)
(2)商品模块完整 API(service/api/product.ts)
(3)业务 API 聚合导出(service/api/index.ts)
一、为什么需要封装 axios
1,封装的优势
(1)在实际项目开发中,直接使用 axios 会存在以下问题:
- 每次请求都需要重复设置 baseURL、timeout 等配置
- 需要在每个请求中手动处理错误和异常
- 请求头中添加 Token 等认证信息需要重复编写
- 响应数据的类型定义分散,不利于维护
(2)通过封装 axios,我们可以实现以下目标:
- 统一配置:所有请求共享相同的 baseURL、timeout 等基础配置
- 统一拦截:在请求拦截器中统一添加 Token,在响应拦截器中统一处理错误
- 类型安全:通过 TypeScript 类型定义,确保请求和响应的数据类型正确
- 代码复用:封装后只需调用封装的方法,避免重复代码
2,封装的三层架构
(1)axios 的封装可以分为三层:
- 全局层:所有请求实例共享的拦截器和配置
- 实例层:单个请求实例特有的拦截器和配置
- 请求层:单个请求特有的配置和处理
(2)分层封装的好处是可以在不同层级添加不同的处理逻辑,例如全局层处理通用错误,实例层处理特定业务的 Token,请求层处理单个请求的特殊需求。
二、HttpClient 基础封装
1,准备工作
(1)首先在项目中创建 service 目录,用于存放 axios 封装相关的文件:
提示:建议将网络请求相关的代码统一放在 service 目录下,便于管理和维护。
src/
└── service/
├── index.ts // 导出请求实例
├── request/
│ ├── index.ts // HttpClient 类实现
│ ├── config.ts // 公共配置
│ └── type.ts // 类型定义
└── api/ // API 接口定义
(2)接着执行如下命令在项目中安装 axios:
npm install axios --save
2,定义类型文件
在 service/request/type.ts 文件中定义类型:
提示:通过继承 AxiosRequestConfig 接口,可以扩展自己的配置属性,如拦截器和 Loading 配置。
import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
// 定义拦截器的类型,T 是响应结果(res.data)的类型
export interface HttpClientInterceptors<T = any> {
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: AxiosResponse<T>) => AxiosResponse<T> | Promise<AxiosResponse<T>>
responseInterceptorCatch?: (error: any) => any
}
// 继承 AxiosRequestConfig,扩展自己的类型
export interface HttpClientConfig<T = any> extends AxiosRequestConfig {
interceptors?: HttpClientInterceptors<T>
showLoading?: boolean // 是否显示 Loading
}
3,创建 HttpClient 类
在 service/request/index.ts 文件中定义 HttpClient 类。该类的代码主要做了三件事情:
- 在构造器中创建 axios 实例,并赋值给 instance 变量
- 编写 request 请求函数,其中 T 指定了响应结果 res.data 的数据类型
- 在 request 函数中调用 instance.request 发起网络请求,并最终返回一个 Promise 对象
import axios from 'axios'
import type { AxiosInstance, AxiosResponse } from 'axios'
import type { HttpClientConfig } from './type'
class HttpClient {
// 声明 axios 实例
instance: AxiosInstance
constructor(config: HttpClientConfig) {
// 创建 axios 实例
this.instance = axios.create(config)
}
// request 方法,T 用于指定响应结果 res.data 的类型
request<T>(config: HttpClientConfig): Promise<AxiosResponse<T>> {
return new Promise((resolve, reject) => {
this.instance
.request<T>(config)
.then((res) => {
// 将结果 resolve 返回出去
resolve(res)
})
.catch((err) => {
reject(err)
return err
})
})
}
}
export default HttpClient
4,创建请求实例
(1)在 service/request/config.ts 文件中编写公共配置:
// 基础 URL
const BASE_URL = 'http://httpbin.org'
// 超时时间
const TIME_OUT = 10000
export { BASE_URL, TIME_OUT }
(2)在 service/index.ts 文件中创建 HttpClient 实例并导出:
import HttpClient from './request'
import { BASE_URL, TIME_OUT } from './request/config'
// 创建 HttpClient 实例
const httpClient = new HttpClient({
baseURL: BASE_URL,
timeout: TIME_OUT
})
export default httpClient
6,使用请求实例
(1)在 src/main.ts 中添加如下代码验证请求功能:
提示:通过泛型 <IResponseData> 指定响应数据的类型,TypeScript 会自动推导出 res.data 的类型,提供更好的类型安全性。
import httpClient from './service'
// 定义响应 res.data 对象的类型
interface IResponseData {
args: any
headers: any
origin: string
url: string
}
// 发起网络请求
httpClient
.request<IResponseData>({
url: '/get',
method: 'get'
})
.then((res) => {
// TypeScript 会自动推导出 res.data 的类型为 IResponseData
console.log(res.data)
})
(2)运行项目,控制台能正常获取到数据说明封装成功。

三、全局拦截器封装
1,添加全局拦截器
(1)修改 HttpClient 类,在构造函数中添加全局拦截器。同时,在响应拦截器中返回 res.data 而不是 res,这样调用方可以直接获取到响应数据,无需再通过 res.data 访问。
注意:由于响应拦截器返回 res.data,request 函数的返回值类型需要改为 Promise<T>。
import axios from 'axios'
import type { AxiosInstance, AxiosResponse } from 'axios'
import type { HttpClientConfig } from './type'
class HttpClient {
// 声明 axios 实例
instance: AxiosInstance
constructor(config: HttpClientConfig) {
// 创建 axios 实例
this.instance = axios.create(config)
// 添加全局请求拦截器
this.instance.interceptors.request.use(
(config) => {
console.log('全局拦截器: 请求成功拦截')
return config
},
(err) => {
console.log('全局拦截器: 请求失败拦截')
return err
}
)
// 添加全局响应拦截器
this.instance.interceptors.response.use(
(res) => {
console.log('全局拦截器: 响应成功拦截')
return res.data // 返回 res.data 而不是 res
},
(err) => {
console.log('全局拦截器: 响应失败拦截')
// 根据 HTTP Error Code 显示不同的错误信息
const status = err.response?.status
switch (status) {
case 401:
// Token 过期,跳转登录
console.log('登录已过期')
// router.push('/login')
break
case 403:
console.log('没有权限访问')
break
case 404:
console.log('请求资源不存在')
break
case 500:
console.log('服务器内部错误')
break
default:
console.log('网络请求失败')
}
return err
}
)
}
// request 函数返回值类型改为 Promise<T>
request<T>(config: HttpClientConfig): Promise<T> {
return new Promise((resolve, reject) => {
this.instance
.request<T, T>(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
}
export default HttpClient
(2)拦截器的执行顺序如下:
- 请求拦截器:先添加的后执行(从后往前)
- 响应拦截器:先添加的先执行(从前往后)
提示:理解拦截器的执行顺序非常重要,特别是在同时使用全局拦截器和实例拦截器时,需要注意它们之间的顺序关系。
2,使用请求实例
(1)在 src/main.ts 中添加如下代码发起请求验证拦截器。
import httpClient from './service'
// 发起网络请求
httpClient
.request({
url: '/get',
method: 'get'
})
.then((data) => {
console.log(data)
})
(2)启动项目,可以看到控制控制台输出如下,说明拦截器配置成功。

四、单个实例拦截器封装
1,为什么需要实例拦截器
全局拦截器适用于所有请求实例,但有时我们需要为特定的请求实例添加独特的拦截逻辑,例如:
- 某些业务模块需要特殊的请求头处理
- 不同模块的错误处理方式不同
- 某些请求需要添加特定的 Token
2,实现实例拦截器
(1)修改 HttpClient 类,支持实例级别的拦截器配置:
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import type { HttpClientInterceptors, HttpClientConfig } from './type'
class HttpClient<T = any> {
instance: AxiosInstance
interceptors?: HttpClientInterceptors<T>
constructor(config: HttpClientConfig<T>) {
this.instance = axios.create(config)
// 从 config 中取出实例的拦截器
this.interceptors = config.interceptors
// 将实例拦截器添加到 axios 实例
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 添加全局拦截器(所有实例共享)
this.instance.interceptors.request.use(
(config) => {
console.log('全局拦截器: 请求成功拦截')
return config
},
(err) => {
console.log('全局拦截器: 请求失败拦截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('全局拦截器: 响应成功拦截')
return res.data
},
(err) => {
console.log('全局拦截器: 响应失败拦截')
const status = err.response?.status
switch (status) {
case 401:
// Token 过期,跳转登录
console.log('登录已过期')
// router.push('/login')
break
case 403:
console.log('没有权限访问')
break
case 404:
console.log('请求资源不存在')
break
case 500:
console.log('服务器内部错误')
break
default:
console.log('网络请求失败')
}
return err
}
)
}
request<T = any>(config: HttpClientConfig): Promise<T> {
return new Promise((resolve, reject) => {
this.instance
.request<T, T>(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
}
export default HttpClient
(2)当同时配置了全局拦截器和实例拦截器,执行顺序如下:
- 请求阶段:实例拦截器 → 全局拦截器
- 响应阶段:全局拦截器 → 实例拦截器
3,为实例添加拦截器
修改 src\service\index.ts 文件,在创建实例时配置拦截器:
提示:实例拦截器在添加时会覆盖 config 中的拦截器配置,请求拦截器是后添加的先执行,响应拦截器是先添加的先执行。
import HttpClient from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const httpClient = new HttpClient({
baseURL: BASE_URL,
timeout: TIME_OUT,
// 为单个实例添加拦截器
interceptors: {
requestInterceptor: (config) => {
const token = 'xxx'
if (token) {
// 统一为 header 添加 Authorization 属性
config.headers!.Authorization = `Bearer ${token}`
}
console.log('实例拦截器: 请求成功拦截')
return config
},
requestInterceptorCatch: (err) => {
console.log('实例拦截器: 请求失败拦截')
return err
},
responseInterceptor: (res) => {
console.log('实例拦截器: 响应成功拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('实例拦截器: 响应失败拦截')
return err
}
}
})
export default httpClient
4,测试拦截器
(1)在 src/main.ts 中添加如下代码发起请求验证拦截器。
import httpClient from './service'
// 发起网络请求
httpClient
.request({
url: '/get',
method: 'get'
})
.then((data) => {
console.log(data)
})
(2)启动项目,可以看到控制台输出如下:

五、GET/POST 等请求方法封装
1,封装 GET/POST 等方法
为 HttpClient 类添加 get、post、delete、patch 方法:
提示:每个请求方法最终调用的都是 request 方法,只是自动设置了对应的 HTTP 方法名,简化了调用方式。
class HttpClient<T = any> {
// ... 其他代码
// GET 请求
get<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
// POST 请求
post<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
// DELETE 请求
delete<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
// PATCH 请求
patch<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HttpClient
2,使用封装的方法
(1)在 src/main.ts 中添加如下代码使用封装后的请求方法。
// GET 请求
httpClient.get<IResponseData>({
url: '/get'
}).then((data) => {
console.log(data) // data 类型为 IResponseData
})
// POST 请求
httpClient.post<IResult>({
url: '/post',
data: { name: 'test' }
}).then((data) => {
console.log(data) // data 类型为 IResult
})
(2)启动项目,可以看到控制台输出如下:

六、Loading 加载效果
1,添加 Loading 功能
修改 HttpClient 类,添加 Loading 功能。这里我们使用 Element Plus 的 Loading 组件,在请求过程中显示加载效果。
注意:Loading 是单例模式,多次调用也不会创建多个,需要在响应后手动关闭。
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import type { HttpClientInterceptors, HttpClientConfig } from './type'
// 导入 Element Plus Loading
import 'element-plus/es/components/loading/style/css'
import { ElLoading } from 'element-plus'
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading'
const DEFAULT_LOADING = true
class HttpClient<T = any> {
instance: AxiosInstance
interceptors?: HttpClientInterceptors<T>
showLoading?: boolean // 是否显示 Loading
loading?: LoadingInstance // Loading 实例
constructor(config: HttpClientConfig<T>) {
this.instance = axios.create(config)
this.showLoading = config.showLoading ?? DEFAULT_LOADING
// 从 config 中取出实例的拦截器
this.interceptors = config.interceptors
// 将实例拦截器添加到 axios 实例
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 添加全局拦截器(所有实例共享)
this.instance.interceptors.request.use(
(config) => {
console.log('全局拦截器: 请求成功拦截')
// 请求拦截器中显示 Loading
if (this.showLoading) {
this.loading = ElLoading.service({
lock: true,
text: 'Loading...',
background: 'rgba(0, 0, 0, 0.2)',
fullscreen: true
})
}
return config
},
(err) => {
console.log('全局拦截器: 请求失败拦截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('全局拦截器: 响应成功拦截')
// 响应拦截器中关闭 Loading
this.loading?.close()
return res.data
},
(err) => {
console.log('全局拦截器: 响应失败拦截')
// 响应拦截器中关闭 Loading
this.loading?.close()
const status = err.response?.status
switch (status) {
case 401:
// Token 过期,跳转登录
console.log('登录已过期')
// router.push('/login')
break
case 403:
console.log('没有权限访问')
break
case 404:
console.log('请求资源不存在')
break
case 500:
console.log('服务器内部错误')
break
default:
console.log('网络请求失败')
}
return err
}
)
}
request<T = any>(config: HttpClientConfig): Promise<T> {
return new Promise((resolve, reject) => {
// 判断单个请求是否需要显示 Loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
.request<T, T>(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
.finally(() => {
// 恢复默认值,不影响下一个请求
this.showLoading = DEFAULT_LOADING
})
})
}
// GET 请求
get<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
// POST 请求
post<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
// DELETE 请求
delete<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
// PATCH 请求
patch<T = any>(config: HttpClientConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HttpClient
2,设置默认显示 Loading
(1)修改 src\service\index.ts 文件,创建实例时默认显示 Loading。
import HttpClient from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const httpClient = new HttpClient({
baseURL: BASE_URL,
timeout: TIME_OUT,
showLoading: true, // 默认显示 Loading
// 为单个实例添加拦截器
interceptors: {
requestInterceptor: (config) => {
const token = 'xxx'
if (token) {
// 统一为 header 添加 Authorization 属性
config.headers!.Authorization = `Bearer ${token}`
}
console.log('实例拦截器: 请求成功拦截')
return config
},
requestInterceptorCatch: (err) => {
console.log('实例拦截器: 请求失败拦截')
return err
},
responseInterceptor: (res) => {
console.log('实例拦截器: 响应成功拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('实例拦截器: 响应失败拦截')
return err
}
}
})
export default httpClient

3,单独控制 Loading 显示
我们可以在单个请求中控制 Loading 的显示,例如个别不需要显示 Loading 的请求可以通过如下方式关闭。
// 单个请求关闭 Loading
httpClient.get({
url: '/get',
showLoading: false // 该请求不显示 Loading
})
附:完整的项目文件组织结构与代码
1,文件组织建议
下面文件结构适配中大型 Vue 前端项目,遵循分层解耦、业务模块化、类型统一管理原则,区分核心请求底层、业务 API、全局类型,结构清晰且易于维护、扩展和多人协作。
- service/request:底层请求基础封装,全局唯一,业务零侵入,所有接口统一复用该底层能力
- service/api:纯业务接口层,按业务模块拆分,只负责定义接口地址、请求方式、参数,不做数据处理、页面逻辑
- service/index.ts:统一导出入口,简化页面引入方式,无需逐个引入业务 API 文件
- types:统一管理所有 TS 类型,区分通用 API 类型和业务专属类型,实现类型复用、全局统一约束
src/
├── service/ # 全局请求服务层(统一处理接口请求、拦截器、配置)
│ ├── index.ts # 统一导出请求实例、所有业务API,供全局引入
│ ├── request/ # 底层请求核心封装(基础能力,不写业务代码)
│ │ ├── index.ts # HttpClient 请求核心类(封装get/post/put/delete、拦截器、错误处理)
│ │ ├── config.ts # 请求公共配置(接口基础地址、超时时间、请求头、白名单等)
│ │ └── type.ts # 底层请求通用类型(请求参数、响应通用结构体、拦截器类型)
│ └── api/ # 业务模块API层(按业务拆分,纯接口请求,无业务逻辑)
│ ├── user.ts # 用户模块相关所有接口
│ ├── product.ts # 商品模块相关所有接口
│ ├── order.ts # 订单模块相关所有接口
│ └── index.ts # 统一聚合导出所有业务API
├── types/ # 全局通用类型声明文件夹
│ ├── api.d.ts # API 全局通用响应类型、基础泛型定义
│ ├── user.d.ts # 用户业务专属类型
│ └── product.d.ts # 商品业务专属类型
└── utils/ # 公共工具方法(可选,配合请求使用)
└── http-error.ts # 请求错误统一处理、提示封装
2,底层请求通用类型
(1)底层请求配置(service/request/config.ts)
// 基础 URL
const BASE_URL = process.env.VUE_APP_API_BASE_URL || '/api'
// 超时时间
const TIME_OUT = 10000
// 无需token的接口白名单
const NO_TOKEN_WHITE_LIST = ['/user/login', '/user/register']
// 导出配置
export { BASE_URL, TIME_OUT, NO_TOKEN_WHITE_LIST }
(2)底层请求通用类型(service/request/type.ts)
import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
// 定义拦截器的类型,T 是响应结果(res.data)的类型
export interface HttpClientInterceptors<T = any> {
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: AxiosResponse<T>) => AxiosResponse<T> | Promise<AxiosResponse<T>>
responseInterceptorCatch?: (error: any) => any
}
// 继承 AxiosRequestConfig,扩展自己的类型
export interface HttpClientConfig<T = any> extends AxiosRequestConfig {
interceptors?: HttpClientInterceptors<T>
showLoading?: boolean // 是否显示 Loading
}
// 后端统一响应通用结构体
export interface HttpResponse<T = any> {
code: number
data: T
message: string
success: boolean
}
(3)核心请求类封装(service/request/index.ts)
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import type { HttpClientInterceptors, HttpClientConfig, HttpResponse } from './type'
// 导入 Element Plus Loading
import 'element-plus/es/components/loading/style/css'
import { ElLoading } from 'element-plus'
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading'
const DEFAULT_LOADING = true
class HttpClient<T = any> {
instance: AxiosInstance
interceptors?: HttpClientInterceptors<T>
showLoading?: boolean // 是否显示 Loading
loading?: LoadingInstance // Loading 实例
constructor(config: HttpClientConfig<T>) {
this.instance = axios.create(config)
this.showLoading = config.showLoading ?? DEFAULT_LOADING
// 从 config 中取出实例的拦截器
this.interceptors = config.interceptors
// 将实例拦截器添加到 axios 实例
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 添加全局拦截器(所有实例共享)
this.instance.interceptors.request.use(
(config) => {
console.log('全局拦截器: 请求成功拦截')
// 请求拦截器中显示 Loading
if (this.showLoading) {
this.loading = ElLoading.service({
lock: true,
text: 'Loading...',
background: 'rgba(0, 0, 0, 0.2)',
fullscreen: true
})
}
return config
},
(err) => {
console.log('全局拦截器: 请求失败拦截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('全局拦截器: 响应成功拦截')
// 响应拦截器中关闭 Loading
this.loading?.close()
return res.data
},
(err) => {
console.log('全局拦截器: 响应失败拦截')
// 响应拦截器中关闭 Loading
this.loading?.close()
const status = err.response?.status
switch (status) {
case 401:
// Token 过期,跳转登录
console.log('登录已过期')
// router.push('/login')
break
case 403:
console.log('没有权限访问')
break
case 404:
console.log('请求资源不存在')
break
case 500:
console.log('服务器内部错误')
break
default:
console.log('网络请求失败')
}
// 统一捕获 400/401/403/500 错误,不抛 runtime 异常
return Promise.resolve({
code: err.response?.status || 500,
data: null,
message: err.message || '请求参数错误',
success: false
})
}
)
}
request<T = any>(config: HttpClientConfig): Promise<HttpResponse<T>> {
return new Promise((resolve, reject) => {
// 判断单个请求是否需要显示 Loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
.request<T, HttpResponse<T>>(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
.finally(() => {
// 恢复默认值,不影响下一个请求
this.showLoading = DEFAULT_LOADING
})
})
}
// GET 请求
get<T = any>(config: HttpClientConfig<T>): Promise<HttpResponse<T>> {
return this.request<T>({ ...config, method: 'GET' })
}
// POST 请求
post<T = any>(config: HttpClientConfig<T>): Promise<HttpResponse<T>> {
return this.request<T>({ ...config, method: 'POST' })
}
// DELETE 请求
delete<T = any>(config: HttpClientConfig<T>): Promise<HttpResponse<T>> {
return this.request<T>({ ...config, method: 'DELETE' })
}
// PATCH 请求
patch<T = any>(config: HttpClientConfig<T>): Promise<HttpResponse<T>> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HttpClient
(4)统一导出请求实例、所有业务 API,供全局引入(service/index.ts)
import HttpClient from './request'
import { BASE_URL, TIME_OUT, NO_TOKEN_WHITE_LIST } from './request/config'
const httpClient = new HttpClient({
baseURL: BASE_URL,
timeout: TIME_OUT,
showLoading: true, // 默认显示 Loading
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
// 为单个实例添加拦截器
interceptors: {
requestInterceptor: (config) => {
console.log('实例拦截器: 请求成功拦截')
// 白名单接口不携带token
if (!NO_TOKEN_WHITE_LIST.includes(config.url || '')) {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
},
requestInterceptorCatch: (err) => {
console.log('实例拦截器: 请求失败拦截')
return err
},
responseInterceptor: (res) => {
console.log('实例拦截器: 响应成功拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('实例拦截器: 响应失败拦截')
// 统一错误处理
return Promise.reject(err)
}
}
})
export default httpClient
// 统一导出所有业务API
export * from './api'
3,将 API 接口按业务模块拆分
(1)用户模块完整 API(service/api/user.ts)
import httpClient from '../index'
// 用户信息基础类型
export interface IUserInfo {
id: number
name: string
email: string
}
// 获取用户信息
export function getUserInfo(id: number) {
return httpClient.get<IUserInfo>({
url: `/user/${id}`
})
}
// 更新用户信息
export function updateUserInfo(data: IUserInfo) {
return httpClient.post<IUserInfo>({
url: '/user/update',
data
})
}
import httpClient from '../index'
// 商品基础信息类型
export interface IProductInfo {
id: number
title: string
price: number
stock: number
categoryId: number
status: 0 | 1 // 0-下架 1-上架
}
// 商品列表查询参数
export interface IProductListParams {
page: number
pageSize: number
categoryId?: number
keyword?: string
}
// 商品列表响应类型
export interface IProductListRes {
list: IProductInfo[]
total: number
page: number
pageSize: number
}
// 获取商品详情
export function getProductDetail(id: number) {
return httpClient.get<IProductInfo>({
url: `/product/${id}`
})
}
// 获取商品分页列表
export function getProductList(params: IProductListParams) {
return httpClient.get<IProductListRes>({
url: '/product/list',
params
})
}
// 新增商品
export function addProduct(data: Omit<IProductInfo, 'id'>) {
return httpClient.post<IProductInfo>({
url: '/product/add',
data
})
}
// 编辑商品
export function updateProduct(data: IProductInfo) {
return httpClient.post<IProductInfo>({
url: '/product/update',
data
})
}
(3)业务 API 聚合导出(service/api/index.ts)
export * from './user' export * from './product'
4,使用示例
(1)这里我使用 mock 工具模拟两个后端接口的返回数据。首先是获取用户信息接口 /user/{id},其返回数据如下:
(2)接着是获取商品分页列表接口 /product/list,其返回数据如下:
(4)启动项目发起请求,可以看到控制台输出内容如下:
{
"code": 200,
"data": {
"id": 1001,
"name": "测试管理员",
"email": "admin@test.com",
"phone": "13800138000",
"avatar": "https://xxx.com/avatar.png",
"createTime": "2026-01-01 10:00:00",
"status": 1
},
"message": "查询成功",
"success": true
}
(2)接着是获取商品分页列表接口 /product/list,其返回数据如下:
{
"code": 200,
"data": {
"list": [
{
"id": 10001,
"title": "高清无线蓝牙耳机",
"price": 199.9,
"stock": 999,
"cover": "https://xxx.com/product1.png",
"categoryId": 10,
"status": 1
},
{
"id": 10002,
"title": "超薄移动电源20000mAh",
"price": 89.9,
"stock": 520,
"cover": "https://xxx.com/product2.png",
"categoryId": 11,
"status": 1
}
],
"total": 56,
"page": 1,
"pageSize": 10
},
"message": "查询成功",
"success": true
}
(3)最后我们在页面中引入相应的 API 进行调用即可。
import { getUserInfo, getProductList } from '@/service'
// 调用用户接口
const fetchUser = async () => {
const res = await getUserInfo(1001)
if (res.success) {
console.log('用户信息', res.data)
}
}
// 调用商品接口
const fetchProductList = async () => {
const res = await getProductList({ page: 1, pageSize: 10 })
if (res.success) {
console.log('商品列表', res.data.list)
}
}

全部评论(0)