typeScript--6实战封装axios
typeScript--6实战封装axios
在vue3 + typeScript 项目中配置axios
项目创建
- 使用npm 创建项目
npm create vite@latest
![过程选择](
完成上面的操作, 就把一个简单的项目创建了,接下来用一个常用的功能来看下 typeScript 是怎么使用的
给项目配置axios 来属性下typeScript 的写法
1.在开始之前,先看下 axios 提供的类的申明文件,对他有个了解, 这是用一个新的插件首先要看的。 目录为 /node_modules/axios/index.t.ts
- 下面 我们看下 Axios
export class Axios {
constructor(config?: AxiosRequestConfig);
defaults: AxiosDefaults;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
delete<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
head<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
options<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
put<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patch<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
postForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
putForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patchForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
}
这里定义的就是 axios 的 类的类型
里面比较熟悉常用的 request, get, post, put, delete, head
在这里 我们看下 request 应为其他几个就是对request 的封装。
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
request 方法有三个泛型,T ,R 和 D,接收一个 AxiosRequestConfig 类型的参数作为配置对象,返回值的类型是一个接收泛型R 的 Promise 类型。
这三个泛型,T 的含义是什么?要去看 R。R 的含义是什么?要去看它的默认类型 AxiosResponse
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
看到这个结构,我们知道了,原来 AxiosResponse 就是在设置响应拦截器中用到的那个 response 对象的类型。
同时也就知道了泛型 T 就是服务器返回的数据的类型。因为服务器究竟返回什么类型,代码是不知道的,所以它的默认类型是 any。
回到头来再来看 request 方法的定义,现在就能明白了,它接收的第一个泛型 T 就是将来服务器返回数据的类型,R 就是这个数据经过 axios 包装一层得到的 response 对象的类型,而 request 方法的返回值是一个 Promise,其值就是成功态的 R,也就是 response对象。
- 下面再通过 AxiosRequestConfig 看下泛型 D 是什么
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: RawAxiosRequestHeaders;
params?: any;
paramsSerializer?: ParamsSerializerOptions;
data?: D;
....
}
相信大家能看出来,它其实就是 axios 的配置对象的类型。在设置请求拦截器和封装公共请求方法时会用到。
- AxiosInstance
该类型为 axios.create 方法创建出的实例的类型。后面直接用到。 - AxiosError
该类型为请求发送过程中出现错误产生的错误对象的类型:
我们会用到其中的 response 属性,它表示响应对象,需要根据它的 HTTP 状态码做一些处理。
开始封装 axios
新建一个 src/utils/request.ts 文件,在这个文件中对 Axios 进行封装。
- Result 类型
从服务器返回的数据类型 一般张这个样子
{
code: 0,
message: '',
data: {}
}
定义一个接口 Result 来表示它,其中 data 的类型不确定,所以就要用到泛型了:
/* 服务器返回数据的的类型,根据接口文档确定 */
export interface Result<T=any> {
code: number,
message: string,
data: T
}
这是一个泛型接口。和泛型函数差不多,在定义时声明一个泛型,用这个泛型去规范成员的类型。将来使用该接口的时候,再去确定泛型的具体类型。
2. 创建实例
import axios from 'axios'
import type { AxiosInstance } from 'axios'
const service: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 30000
})
- 请求拦截器
import type { AxiosError, AxiosRequestConfig } from 'axios'
import { message as Message } from 'ant-design-vue'
/* 请求拦截器 */
service.interceptors.request.use((config: AxiosRequestConfig) => {
// 伪代码
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config
}, (error: AxiosError) => {
Message.error(error.message);
return Promise.reject(error)
})
- 响应拦截器
响应拦截器大家也很熟悉了。简单说一下做了哪些事情:
- 根据自定义错误码判断请求是否成功,然后只将组件会用到的数据,也就是上面的 Result 中的 data 返回
- 如果错误码判断请求失败,此时为业务错误,比如用户名不存在等,在这里进行提示
- 如果网络错误,则进入第二个回调函数中,根据不同的状态码设置不同的提示消息进行提示
import type { AxiosError, AxiosResponse } from 'axios'
import { message as Message } from 'ant-design-vue'
/* 响应拦截器 */
service.interceptors.response.use((response: AxiosResponse) => {
const { code, message, data } = response.data
// 根据自定义错误码判断请求是否成功
if (code === 0) {
// 将组件用的数据返回
return data
} else {
// 处理业务错误。
Message.error(message)
return Promise.reject(new Error(message))
}
}, (error: AxiosError) => {
// 处理 HTTP 网络错误
let message = ''
// HTTP 状态码
const status = error.response?.status
switch (status) {
case 401:
message = 'token 失效,请重新登录'
// 这里可以触发退出的 action
break;
case 403:
message = '拒绝访问'
break;
case 404:
message = '请求地址错误'
break;
case 500:
message = '服务器故障'
break;
default:
message = '网络连接故障'
}
Message.error(message)
return Promise.reject(error)
})
- 公共请求方法
这里是本文的重点,也是 TS 封装 Axios 的重点。使用 TS 无非要获得良好的代码提示,我们在调用接口时编辑器能提示出该接口的返回值有哪些。
export const http = {
get<T=any>(url: string, config?: AxiosRequestConfig) : Promise<T> {
return service.get(url, config)
},
post<T=any>(url: string, data?: object, config?: AxiosRequestConfig) :Promise<T> {
return service.post(url, data, config)
},
put<T=any>(url: string, data?: object, config?: AxiosRequestConfig) :Promise<T> {
return service.put(url, data, config)
},
delete<T=any>(url: string, config?: AxiosRequestConfig) : Promise<T> {
return service.delete(url, config)
}
}
我们以 get 方法为例,看下为什么这么封装?
上文说过,axios 的 requet,get 这些方法,返回的都是一个 AxiosResponse 类型的数据。
默认的响应拦截器返回的是完整的 response 对象,类型为 AxiosResponse 。
经过我们的修改,现在响应拦截器只返回 response.data.data,对应的类型就是
AxiosResponse
所以我们要手动帮助编辑器“修正”类型提示,也就是不依靠 TS 的类型推导,而是主动设置返回类型为 Promise :
这里的泛型 T 表示的服务器返回数据的类型,具体返回什么类型,要到接口调用时才知道。所以下面来到 API 层,去封装请求接口。
之所以封装这一层公共请求方法,主要目的就是为了帮编译器正确识别类型。方法就是手动设置返回类型。如果直接使用 axios 实例的请求方法,就需要在每次调用时都指定一遍,当接口有几十上百个时,多少会在增加一些工作量。所以就在这里多加一层,提前将类型制定好。
API层
准备两个文件,一个写类型,一个写接口。
在 api/user/types.ts 文件中,写接口需要的参数的类型,和接口返回数据的类型。这里的类型,要根据接口文档而定。
/* 登录接口参数类型 */
export interface LoginData {
username: string,
password: string,
}
/* 登录接口返回值类型 */
export interface LoginRes {
token: string
}
/* 用户信息接口返回值类型 */
export interface UserInfoRes {
id: string,
username: string,
avatar: string,
description: string,
}
在 api/user/index.ts 文件中,封装组件用到的接口。
import request, { http } from '@/utils/request'
import type { LoginData, LoginRes, UserInfoRes} from './types'
/**
* 登录
*/
export function login(data: LoginData) {
return http.post<LoginRes>('/user/login', data);
}
/**
* 获取登录用户信息
*/
export function getUserInfo() {
return http.get<UserInfoRes>('/user/info')
}
再这里使用的时候 调用 login 接口, 定义了
http.post<loginRes>
通过传入响应参数的类型,现在 Axios 经过 TypeScript 的封装,可以给出友好的类型提示,这对于开发体验和效率都有很好的提升。