使用TypeScript 封装axios
优点
如果我们在项目中直接使用axios
,项目中每个模块对axios
的依赖性都很强。假如有一天axios
因为某些原因不再维护而要用到另一个库的时候,我们就要全局一个一个的把axios
给替换掉。这样对项目后续的维护升级不方便
我们把axios
封装在一个文件,让axios
跟其他模块的耦合度降低,以后要更换成别的库的话可以直接在这个文件里面更换即可。
为什么使用TypeScript
?
可以对代码进行类型校验,让我们的代码更加安全可靠
封装要点
- 可以创建不同
axios
实例,这样每个实例都可以定义自己baseURL
等属性 - 每个
axios
实例除了可以传入基本属性外,还可以传入自定义的拦截器 - 每个单独的请求可以自定义拦截器
开始
安装
npm install axios -D
封装请求类
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
class LFRequest {
// 有时候我们不同的请求需要不同的baseUrl,如果在同一个axios上定义会被覆盖掉, 我们可以用axios.create(config)创建不同的实例
instance: AxiosInstance
constructor(config: AxiosRequestConfig) {
// 一开始我们不知道要用什么类型 鼠标放在create上 ctrl+点击可以到达源码
// 创建axios实例
// 有时候我们不同的请求需要不同的baseUrl,如果在同一个axios实例上定义会被覆盖掉, 我们可以创建不同的实例
this.instance = axios.create(config)
}
request(config: AxiosRequestConfig): void {
this.instance.request(config).then((res) => {
console.log(res)
})
}
}
// 使用
const lfRequest = new LFRequest({
baseURL: '自己定义',
timeout: '1000'
})
封装拦截器
拦截器应用
- 每次请求在请求头带上token或者cookie
- 请求没有返回的结果时候显示loading动画
可以根据需要是否带上cookie以及显示loading动画
拦截器使用
axios.interceptors.request.use(fn1, fn2)
axios.interceptors.response.use(fn1, fn2)
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
我们在创建新的axios
实例的时候除了传入基本属性外还可以传入自定义的拦截器, 例如
const lfRequest = new LFRequest({
baseURL: '自己定义',
timeout: '1000',
interceptors: {} // 拦截器对象
})
某个axios
实例独有的拦截器
定义拦截器函数接口
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
// 泛型类
interface LFRequestInterceptors {
// 函数
// 源码
// interceptors: {
// request: AxiosInterceptorManager<AxiosRequestConfig>; // 类型 AxiosRequestConfig
// response: AxiosInterceptorManager<AxiosResponse>;
// };
// 泛型类 V:AxiosRequestConfig T = V
// export interface AxiosInterceptorManager<V> {
// use<T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number;
// eject(id: number): void;
// }
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorsCatch?: (error: any) => any
responseInterceptor: (res: AxiosResponse) => AxiosResponse
responseInterceptorCatch: (error: any) => any
}
拓展 AxiosRequestConfig
我们希望创建axios
实例除了传入基础参数外,还可以传入拦截器函数
interface LFRequestConfig extends AxiosRequestConfig {
interceptors?: LFRequestInterceptors
}
传入拦截器函数时进行拦截
const lfRequest = new LFRequest({
baseURL: '自己定义',
timeout: 1000,
interceptors: {
requestInterceptor(config) {
console.log('成功拦截')
return config
},
requestInterceptorCatch: (err) => {
console.log('请求失败的拦截')
return err
},
responseInterceptor: (res) => {
console.log('响应成功的拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('响应失败的拦截')
return err
}
}
})
class LFRequest {
instance: AxiosInstance
// 存放实例传入拦截器函数
interceptors?: LFRequestInterceptors
constructor(config: LFRequestConfig) {
// 创建axios实例
this.instance = axios.create(config)
// 保存实例传入拦截器函数
this.interceptors = config.interceptors
// 请求拦截器/响应拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptors,
this.interceptors?.requestInterceptorsCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
}
request(config: AxiosRequestConfig): void {
this.instance.request(config).then((res) => {
console.log(res)
})
}
}
所有axios
实例都有的拦截器
class LFRequest {
constructor(config: LFRequestConfig) {
// ... 省略一些代码
// 每个实例都有的请求拦截器 每次创建实例的时候这些拦截器也会被创建
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
}, (err) => {
console.log('所有实例都有的拦截器: 响应失败拦截')
return err
})
}
}
某次请求自定义拦截器
lfRequest.request({
url: '路径',
method: 'GET',
interceptors: {
requestInterceptor: (config) => {
console.log('单独请求的config')
return config
},
requestInterceptorCatch: (err) => {
console.log(err, '请求失败的拦截')
return err
}
}
})
class LFRequest {
// ...代码省略
// AxiosRequestConfig 替换成扩展的接口 LFRequestConfig
request(config: LFRequestConfig): void {
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
// 调用requestInterceptor会返回config
config = config.interceptors.requestInterceptor(config)
}
this.instance.request(config).then((res) => {
console.log(res)
})
}
}
使用拦截器在请求头上加token
const lfRequest = new LFRequest({
baseURL: 'lalala',
timeout: 10000,
// 这样每个实例都有不同的拦截器
interceptors: {
requestInterceptor(config) {
// 一般token放在vuex或者缓存中
const token = ''
// config.headers 类型检查不通过 有两种方式
// 第一种
// if (!config.headers) {
// config.headers = {}
// }
// 第二种
config.headers = config.headers ?? {}
if (token) {
// Bearer 信差
config.headers.Authorization = `Bearer ${token}`
}
return config
}
}
})
请求未响应时显示loading
假如我们要给axios
实例每个请求都加上loading效果
// element-plus 版本 2.1.9
import { ElLoading } from 'element-plus'
// 按需引入,如果不引入css 样式不生效
import 'element-plus/theme-chalk/base.css' // 不同大版本放置的目录不同
import 'element-plus/theme-chalk/el-loading.css'
class LFRequest {
// ...代码省略
// 加载实例
loading: any
this.instance.interceptors.request.use((config) => {
console.log('所有实例都有的拦截器: 请求成功拦截')
// 初始化loading
this.loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.5)'
})
return config
},(err) => {
console.log('所有实例都有的拦截器: 请求失败拦截')
return err
})
// 每个实例都有的响应拦截器
this.instance.interceptors.response.use((res) => {
// 将loading移除
this.loading?.close()
console.log('所有实例都有的拦截器: 响应成功拦截')
return res
},(err) => {
// 将loading移除
this.loading?.close()
console.log('所有实例都有的拦截器: 响应失败拦截')
return err
})
}
某次请求决定要不要显示loading
// 扩展request的配置参数
interface LFRequestConfig extends AxiosRequestConfig {
interceptors?: LFRequestInterceptors
showLoading?: boolean
}
class LFRequest {
loading: any
showLoading?: boolean
constructor(config: LFRequestConfig) {
// 每个实例都有的请求拦截器
this.instance.interceptors.request.use(
(config) => {
console.log('所有实例都有的拦截器: 请求成功拦截')
if (this.showLoading) {
this.loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.5)'
})
}
return config
},
(err) => {
console.log('所有实例都有的拦截器: 请求失败拦截')
return err
}
)
// 每个实例都有的响应拦截器
this.instance.interceptors.response.use(
(res) => {
// 将loading移除
this.loading?.close()
console.log('所有实例都有的拦截器: 响应成功拦截')
return res
},
(err) => {
// 将loading移除
this.loading?.close()
console.log('所有实例都有的拦截器: 响应失败拦截')
return err
}
)
}
request(config: LFRequestConfig): void {
// 判断这次请求是否要显示loading
this.showLoading = config.showLoading ?? true
}
}
lfRequest.request({
url: 'lalala',
method: 'GET',
showLoading: false
})
优化请求响应参数
接口返回的数据是这样的
但是通过request
请求拿到的数据,axios
会进行一层封装,只有data里面的数据是我们需要的
// 返回一个promise
request(config: LFRequestConfig): Promise<AxiosResponse> {
return new Promise((resolve, reject) => {
this.showLoading = config.showLoading ?? true
// 1.单个请求对请求config的处理
// 请求拦截器
if (config.interceptors?.requestInterceptor) {
// 调用requestInterceptor会返回config
config = config.interceptors.requestInterceptor(config)
}
this.instance
.request(config)
.then((res) => {
// 响应拦截器
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 将showLoading设置false, 这样不会影响下一个请求
this.showLoading = false
resolve(res.data)
})
.catch((err) => {
this.showLoading = false
reject(err)
})
})
}
有时候我们知道后端返回的数据结构以及格式,我们可以定义返回值的类型,那样我们使用返回值的时候可以有很好的提示,也不容易出现一些低级错误。
interface DataType {
data: any
returnCode: string
success: boolean
}
// 泛型
lfRequest.request<Datatype>({
// 代码省略
})
// 修改request方法
// 把 T 一层一层传递 : Promise<T> 把T传递给返回值
// LFRequestConfig<T> 把T传给拦截器
request<T>(config: LFRequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.showLoading = config.showLoading ?? true
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
// 调用requestInterceptor会返回config
config = config.interceptors.requestInterceptor(config)
}
// 通过request 把 T 传递进去
this.instance.request<any, T>(config).then((res) => {
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 将showLoading设置false, 这样不会影响下一个请求
this.showLoading = false
resolve(res)
}).catch((err) => {
// 将showLoading设置false, 这样不会影响下一个请求
this.showLoading = false
reject(err)
})
})
}
// 通过 LFRequestConfig 接口 把 T传给拦截器
interface LFRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: LFRequestInterceptors<T>
showLoading?: boolean
}
interface LFRequestInterceptors<T = AxiosResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: T) => T
responseInterceptorCatch?: (error: any) => any
}
封装GET
, POST
等请求方法
class LFRequest {
// 代码省略
get<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
patch<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
加油,每天进步一点点!!!