Axios的TS版本封装
最近开发的一些项目都用上了vue3.0+ts,所以针对一些全局使用的工具也进行了一些封装。Axios作为HTTP请求的工具,首当其冲。
封装思考
- axios的基础配置
- 请求拦截的封装
- 响应拦截的封装
- 取消重复请求
- 请求参数以及回参的泛型化--主要是完成这一步最大化享受到ts带来的类型检测以及类型推断的好处
了解axios声明文件
下面我将声明文件中在封装用到的一些摘抄出来,其实了解一个插件,最快的方法就是了解它的声明文件,提供什么方法有什么参数什么类型的参数一目了然。
- 首先可以看到axios支持的请求方式
Method - 响应数据支持的类型
ResponseType - 请求配置
AxiosRequestConfig,其中就包括请求方式,请求类型,参数,以及一些请求头的配置 - 响应数据类型
AxiosResponse,从这里可以了解到axios响应数据的数据结构 - 请求异常的数据类型
AxiosError - 取消请求的方法
Axios支持的api
export type Method =
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'purge' | 'PURGE'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'
export type ResponseType =
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream'
export interface AxiosRequestConfig<T = any> {
url?: string;
method?: Method;
baseURL?: string;
transformRequest?: AxiosTransformer | AxiosTransformer[];
transformResponse?: AxiosTransformer | AxiosTransformer[];
headers?: Record<string, string>;
params?: any;
paramsSerializer?: (params: any) => string;
data?: T;
timeout?: number;
timeoutErrorMessage?: string;
withCredentials?: boolean;
adapter?: AxiosAdapter;
auth?: AxiosBasicCredentials;
responseType?: ResponseType;
xsrfCookieName?: string;
xsrfHeaderName?: string;
onUploadProgress?: (progressEvent: any) => void;
onDownloadProgress?: (progressEvent: any) => void;
maxContentLength?: number;
validateStatus?: ((status: number) => boolean) | null;
maxBodyLength?: number;
maxRedirects?: number;
socketPath?: string | null;
httpAgent?: any;
httpsAgent?: any;
proxy?: AxiosProxyConfig | false;
cancelToken?: CancelToken;
decompress?: boolean;
transitional?: TransitionalOptions
signal?: AbortSignal;
}
export interface AxiosResponse<T = never> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: AxiosRequestConfig<T>;
request?: any;
}
export interface AxiosError<T = never> extends Error {
config: AxiosRequestConfig;
code?: string;
request?: any;
response?: AxiosResponse<T>;
isAxiosError: boolean;
toJSON: () => object;
}
export interface Cancel {
message: string;
}
export interface Canceler {
(message?: string): void;
}
export interface CancelTokenStatic {
new (executor: (cancel: Canceler) => void): CancelToken;
source(): CancelTokenSource;
}
export interface CancelToken {
promise: Promise<Cancel>;
reason?: Cancel;
throwIfRequested(): void;
}
export interface CancelTokenSource {
token: CancelToken;
cancel: Canceler;
}
export class Axios {
constructor(config?: AxiosRequestConfig);
defaults: AxiosRequestConfig;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = never, R = AxiosResponse<T>> (config: AxiosRequestConfig<T>): Promise<R>;
get<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;
delete<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;
head<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;
options<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;
post<T = never, R = AxiosResponse<T>>(url: string, data?: T, config?: AxiosRequestConfig<T>): Promise<R>;
put<T = never, R = AxiosResponse<T>>(url: string, data?: T, config?: AxiosRequestConfig<T>): Promise<R>;
patch<T = never, R = AxiosResponse<T>>(url: string, data?: T, config?: AxiosRequestConfig<T>): Promise<R>;
}
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosStatic extends AxiosInstance {
create(config?: AxiosRequestConfig): AxiosInstance;
Cancel: CancelStatic;
CancelToken: CancelTokenStatic;
Axios: typeof Axios;
readonly VERSION: string;
isCancel(value: any): boolean;
all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
isAxiosError(payload: any): payload is AxiosError;
}
declare const axios: AxiosStatic;
export default axios;
实现一个Request的类
- 首先先把关键性的字段声明下
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosError,
AxiosResponse,
CancelTokenStatic
} from 'axios'
class Request {
instance: AxiosInstance // axios实例
pending:Array<{
url: string
cancel: Function
}> = [] // 请求url集合在每次请求之前都会查找是否存在当前接口请求,存在则取消并建立新的请求
CancelToken: CancelTokenStatic = axios.CancelToken
axiosRequestConfig: AxiosRequestConfig = {} // 请求配置
successCode: Array<number> = [200,201,204] // 成功的相应状态码
baseURL:string='/api'
constructor() {
// 这里会初始化axios的配置并且绑定请求以及相应的拦截配置
this.requestConfig()
this.instance = axios.create(this.axiosRequestConfig)
this.interceptorsRequest()
this.interceptorsResponse()
}
async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<MyResponseType<T>> {}
async post<T = any>(url: string,data?: T,config?: AxiosRequestConfig): Promise<MyResponseType<T>> {}
requestConfig():void{}
interceptorsRequest():void{}
interceptorsResponse():void{}
requestConfig():void{}
// 取消重复请求
removePending(config: AxiosRequestConfig): void{}
// 请求日志
requestLog(request: AxiosRequestConfig): void {}
// 响应日志
responseLog(response: AxiosResponse): void {}
}
export default new Request()
那其实完成以上的内容,大体的框架就已经完成了。接着就是把每个阶段的逻辑加入进去。
- axios的基础配置
- 请求拦截器中需要处理参数类型以及自定义请求头的设置,并且移除之前重复请求的接口并且加入当前请求接口到peding,打印请求日志
- 响应拦截器中针对除了successCode以外的相应状态码进行统一处理并且从peding移除当前接口请求,打印响应日志
- 请求方式的实现,其实就是在对应的方法里面调用axios对应的请求然后处理一下响应数据并返回
以下是完整代码:
// import http from 'http'
// import https from 'https'
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosError,
AxiosResponse,
CancelTokenStatic
} from 'axios'
import qs from 'qs'
import { store } from '../store'
import { MyResponseType } from './types'
export class Request {
protected instance: AxiosInstance
protected pending: Array<{
url: string
cancel: Function
}> = []
protected CancelToken: CancelTokenStatic = axios.CancelToken
protected axiosRequestConfig: AxiosRequestConfig = {}
protected successCode: Array<Number> = [200, 201, 204]
protected baseURL: string = '/api'
constructor() {
this.requestConfig()
this.instance = axios.create(this.axiosRequestConfig)
this.interceptorsRequest()
this.interceptorsResponse()
}
async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<MyResponseType<T>> {
try {
const data: MyResponseType<T> = await this.instance.get(url, config)
return data
} catch (err: any) {
const message = err.message || '请求失败'
return {
code: -1,
message,
data: null as any
}
}
}
async post<T = any>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<MyResponseType<T>> {
try {
const res: MyResponseType<T> = await this.instance.post(url, data, config)
return res
} catch (err: any) {
const message = err.message || '请求失败'
return {
code: -1,
message,
data: null as any
}
}
}
// axios请求配置
protected requestConfig(): void {
this.axiosRequestConfig = {
baseURL: this.baseURL,
headers: {
timestamp: String(new Date().getTime()),
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
transformRequest: [(obj) => qs.stringify(obj)],
transformResponse: [
(data: AxiosResponse) => {
return data
}
],
paramsSerializer(params: any) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
timeout: 30000,
withCredentials: false,
responseType: 'json',
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxRedirects: 5,
maxContentLength: 2000,
validateStatus(status: number) {
return status >= 200 && status < 500
}
// httpAgent: new http.Agent({ keepAlive: true }),
// httpsAgent: new https.Agent({ keepAlive: true })
}
}
// 请求拦截
protected interceptorsRequest() {
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
this.removePending(config)
config.cancelToken = new this.CancelToken((c: any) => {
this.pending.push({
url: `${config.url}/${JSON.stringify(config.data)}&request_type=${config.method}`,
cancel: c
})
})
const token = store.getters['userModule/getToken']
if (token) {
Object.assign(config.headers, { 'x-token': token || '' })
}
this.requestLog(config)
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
}
// 响应拦截
protected interceptorsResponse(): void {
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
this.responseLog(response)
this.removePending(response.config)
if (this.successCode.indexOf(response.status) === -1) {
// Message({
// message: response.data.message || 'Error',
// type: 'error',
// duration: 5 * 1000
// })
// if (response.data.code === 401) {
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// UserModule.ResetToken()
// location.reload()
// })
// }
return Promise.reject(new Error(response.data || 'Error'))
}
return response.data
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
}
// 取消重复请求
protected removePending(config: AxiosRequestConfig): void {
this.pending.map((v, index) => {
if (v.url === `${config.url}/${JSON.stringify(config.data)}&request_type=${config.method}`) {
v.cancel()
console.log('=====', this.pending)
this.pending.splice(index, 1)
console.log('+++++', this.pending)
}
return v
})
}
// 请求日志
protected requestLog(request: AxiosRequestConfig): void {
if (process.env.NODE_ENV === 'development') {
const randomColor = `rgba(${Math.round(Math.random() * 255)},${Math.round(
Math.random() * 255
)},${Math.round(Math.random() * 255)})`
console.log(
'%c┍------------------------------------------------------------------┑',
`color:${randomColor};`
)
console.log('| 请求地址:', request.url)
console.log(
'| 请求参数:',
qs.parse(
((request.method || 'get').toLowerCase() === 'get' ? request.params : request.data) as any
)
)
console.log(
'%c┕------------------------------------------------------------------┙',
`color:${randomColor};`
)
}
}
// 响应日志
protected responseLog(response: AxiosResponse): void {
if (process.env.NODE_ENV === 'development') {
const randomColor = `rgba(${Math.round(Math.random() * 255)},${Math.round(
Math.random() * 255
)},${Math.round(Math.random() * 255)})`
console.log(
'%c┍------------------------------------------------------------------┑',
`color:${randomColor};`
)
console.log('| 请求地址:', response.config.url)
console.log('| 请求参数:', qs.parse(response.config.data as any))
console.log('| 返回数据:', response.data)
console.log(
'%c┕------------------------------------------------------------------┙',
`color:${randomColor};`
)
}
}
}
export default new Request()