在现代web开发中,与后端服务进行稳定且高效的通信是至关重要的。为了达到这一目标,Axios作为一个基于Promise的HTTP客户端,因其简洁的API和强大的功能而广受欢迎。而TypeScript,作为JavaScript的超集,提供了静态类型检查,让开发者能够在编写代码的时候就能发现潜在的错误,从而提高了代码的健壮性和可维护性。本文将探讨如何将Axios和TypeScript结合使用,并提供一个封装实践的例子,以展示如何创建一个类型安全且易于使用的HTTP服务。
一.目录结构
二.请求类封装
typescript
import axios from 'axios';
import type { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import type { MyInternalRequestConfig, MyRequestConfig } from './type'
import { getToken } from '../../utils/auth';
// 拦截器 蒙版Loading/token/修改配置
class MyRequest {
instance: AxiosInstance;
// request示例 => axios实例
constructor(config: MyRequestConfig) {
this.instance = axios.create(config)
// 每个instance都拦截器
this.instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// loading/token
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
console.log("全局请求成功拦截")
return config;
}, (error: AxiosError) => {
console.log("全局请求失败的拦截", error)
return Promise.reject(error)
})
// 响应拦截
this.instance.interceptors.response.use(
(res: AxiosResponse) => {
console.log("全局响应成功的拦截")
const { data } = res
// 处理res.code
return data;
}, (error: AxiosError) => {
console.log("全局响应失败的拦截", error)
const { message } = error
// 根据message 和status处理错误
this.handleErrorMessage(message)
return Promise.reject(error)
})
// 针对具有自定义拦截器的config
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessInterceptor,
config.interceptors?.requestFailInterceptor
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessInterceptor,
config.interceptors?.responseFailInterceptor
)
}
// 封装错误消息处理
handleErrorMessage(message: string) {
}
// 封装网络请求方法
request<T = any>(config: MyRequestConfig<T>) {
// 单次拦截器
if (config.interceptors?.requestSuccessInterceptor) {
console.log("url请求成功拦截", config.url)
config = config.interceptors.requestSuccessInterceptor(config as MyInternalRequestConfig)
}
return new Promise<T>((resolve, reject) => {
try {
// 在尝试发送请求之前,捕获可能的配置错误
this.instance.request<any, T>(config).then(res => {
if (config.interceptors?.responseSuccessInterceptor) {
console.log("url响应成功拦截", config.url)
res = config.interceptors.responseSuccessInterceptor(res)
}
resolve(res)
}).catch(error => {
if (config.interceptors?.responseFailInterceptor) {
console.log("url响应失败拦截", config.url)
error = config.interceptors.responseFailInterceptor(error)
}
reject(error)
})
} catch (e: any) {
if (config.interceptors?.requestFailInterceptor) {
console.log("url请求失败拦截", config.url)
const modifiedError = config.interceptors.requestFailInterceptor(e)
return Promise.reject(modifiedError)
} else {
return Promise.reject(e)
}
}
})
}
// 封装常用方法
get<T = any>(config: MyRequestConfig<T>) {
return this.request({ ...config, method: "GET" })
}
post<T = any>(config: MyRequestConfig<T>) {
return this.request({ ...config, method: "POST" })
}
put<T = any>(config: MyRequestConfig<T>) {
return this.request({ ...config, method: "PUT" })
}
delete<T = any>(config: MyRequestConfig<T>) {
return this.request({ ...config, method: "DELETE" })
}
}
export default MyRequest;
MyRequest
类通过构造函数初始化一个Axios实例,并对其进行配置和拦截器的设置。构造函数接收一个MyRequestConfig
类型的参数,这个参数类型可能是对Axios请求配置的扩展。
js
this.instance = axios.create(config)
上面的代码创建了一个新的Axios实例,并将传入的配置应用到这个实例上。 在构造函数中设置了全局的请求和响应拦截器。
请求拦截器 :在请求发送前,会检查是否有可用的token,并将其以Bearer Token
的形式添加到请求头中。同时在控制台打印出"全局请求成功拦截"的信息。如果请求失败,会在控制台打印出失败信息。
响应拦截器 :在响应接收后,会在控制台打印出"全局响应成功的拦截"的信息,并返回响应的数据。如果响应失败,会调用handleErrorMessage
方法处理错误信息,并返回rejected的Promise。
自定义拦截器设置
此外,还可以为特定的实例设置自定义的拦截器。这些自定义拦截器是通过构造函数的配置参数传入的。
typescript
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessInterceptor,
config.interceptors?.requestFailInterceptor
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessInterceptor,
config.interceptors?.responseFailInterceptor
)
请求方法封装
MyRequest
类还提供了一个request
方法,用于发起网络请求。这个方法接收一个MyRequestConfig
类型的参数,并返回一个Promise。在这个方法内部,可以设置单次请求的拦截器,并发起请求。
此外,还提供了一些常用HTTP方法的封装,如get
, post
, put
, delete
等,这些方法内部都是调用的request
方法。
ts
request<T = any>(config: MyRequestConfig<T>) {
// 单次拦截器
if (config.interceptors?.requestSuccessInterceptor) {
console.log("url请求成功拦截", config.url)
config = config.interceptors.requestSuccessInterceptor(config as MyInternalRequestConfig)
}
return new Promise<T>((resolve, reject) => {
try {
// 在尝试发送请求之前,捕获可能的配置错误
this.instance.request<any, T>(config).then(res => {
if (config.interceptors?.responseSuccessInterceptor) {
console.log("url响应成功拦截", config.url)
res = config.interceptors.responseSuccessInterceptor(res)
}
resolve(res)
}).catch(error => {
if (config.interceptors?.responseFailInterceptor) {
console.log("url响应失败拦截", config.url)
error = config.interceptors.responseFailInterceptor(error)
}
reject(error)
})
} catch (e: any) {
if (config.interceptors?.requestFailInterceptor) {
console.log("url请求失败拦截", config.url)
const modifiedError = config.interceptors.requestFailInterceptor(e)
return Promise.reject(modifiedError)
} else {
return Promise.reject(e)
}
}
})
}
错误处理
handleErrorMessage
方法用于处理错误信息,目前是一个空方法,需要根据实际需求来实现。
类型文件
ts
//request/type.ts
import { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';;
// 针对AxiosRequestConfig进行扩展
export interface MyInterceptors<T = AxiosResponse> {
requestSuccessInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
requestFailInterceptor?: (error: AxiosError) => AxiosError;
responseSuccessInterceptor?: (res: T) => T;
responseFailInterceptor?: (error: AxiosError) => AxiosError;
}
export interface MyRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: MyInterceptors<T>;
}
export interface MyInternalRequestConfig extends InternalAxiosRequestConfig {
interceptors?: MyInterceptors;
}
MyInterceptors<T = AxiosResponse>
MyInterceptors
接口定义了四个可选的属性,每个属性都是一个函数,用于处理请求或响应的成功或失败情况。这些函数的参数和返回类型与Axios的拦截器保持一致。
requestSuccessInterceptor
: 请求成功时执行的函数,接收一个InternalAxiosRequestConfig
类型的参数,返回修改后的请求配置。requestFailInterceptor
: 请求失败时执行的函数,接收一个AxiosError
类型的参数,返回处理后的错误。responseSuccessInterceptor
: 响应成功时执行的函数,接收一个泛型T
类型的参数(默认为AxiosResponse
),返回处理后的响应数据。responseFailInterceptor
: 响应失败时执行的函数,接收一个AxiosError
类型的参数,返回处理后的错误。
MyRequestConfig<T = AxiosResponse>
MyRequestConfig
接口继承自AxiosRequestConfig
,在Axios的请求配置基础上进行了扩展。它增加了一个可选的interceptors
属性,其类型为MyInterceptors<T>
。
MyInternalRequestConfig
MyInternalRequestConfig
接口继承自InternalAxiosRequestConfig
,并增加了一个可选的interceptors
属性,其类型为MyInterceptors
。
创建请求实例
ts
import { BASEURL, TIMEOUT } from "./config/carbon";
import MyRequest from "./request";
const myRequest = new MyRequest({
baseURL: BASEURL,
timeout: TIMEOUT,
withCredentials: true,
headers: {
"Content-Type": "application/json;charset=utf-8"
},
interceptors: {
requestSuccessInterceptor: (config) => {
return config
},
requestFailInterceptor: (error) => {
return error;
},
responseSuccessInterceptor: (res) => {
return res
},
responseFailInterceptor: (error) => {
return error;
}
}
})
export {
myRequest,
}
在创建实例时,传入了一个配置对象,其中包含了Axios请求的基本设置和拦截器的定义。
baseURL
: 设置请求的基础URL。timeout
: 设置请求的超时时间。withCredentials
: 设置跨域请求是否需要凭证。headers
: 设置请求头,这里设置了内容类型为JSON。
另外,定义了四个拦截器:
requestSuccessInterceptor
: 请求成功时的拦截器,这里简单地返回了原始的请求配置。requestFailInterceptor
: 请求失败时的拦截器,返回了原始的错误对象。responseSuccessInterceptor
: 响应成功时的拦截器,返回了原始的响应数据。responseFailInterceptor
: 响应失败时的拦截器,返回了原始的错误对象。
通过这种方式,可以确保在整个应用中使用统一的请求配置和处理逻辑,提高代码的可维护性和一致性。
使用
创建api请求函数文件
ts
import { myRequest } from "..";
export function getUserList() {
return myRequest.request({
url: "/test/user/list",
method: "GET",
})
}
// 添加独自的拦截器
export function getUserById(userId: number) {
return myRequest.get({
url: `/test/user/${userId}`,
interceptors: {
requestSuccessInterceptor: (config) => {
return config
},
requestFailInterceptor: (error) => {
return error
},
responseSuccessInterceptor(res) {
return res
},
responseFailInterceptor(error) {
return error
}
}
})
}
getUserList
函数通过myRequest
实例发送一个GET请求,目标URL为"/test/user/list"
。这个函数直接使用了myRequest
实例的request
方法,并传入了请求的配置对象。 getUserById
函数通过myRequest
实例发送一个GET请求,目标URL为"/test/user/${userId}"
,其中${userId}
是函数参数中传入的用户ID。 这个函数使用了myRequest
实例的get
方法,并为这个特定请求设置了自己的拦截器。这四个拦截器分别处理请求成功、请求失败、响应成功和响应失败的情况。
在React组件中使用
ts
import React, { useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
import { getUserById, getUserList } from "./service/api/user";
function App() {
const fetchUserData = async () => {
const res = await getUserById(1);
console.log(res);
};
useEffect(() => {
fetchUserData();
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
参考自corderwhy老师