视频:项目实战3
封装思想
为了满足复杂项目的业务代码需求,需要对axios库进行封装。文中整体将axios封装成为一个类,这样需要进行网络请求时需要new一个实例出来,然后再进行调用。
axios拦截器
在使用 Axios 发送网络请求时,拦截器是非常有用的工具,它可以让你在请求发送和响应返回阶段添加自定义逻辑。拦截器分为请求拦截器和响应拦截器。
- 请求拦截器
在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装;
请求拦截器允许你在发送请求之前对请求进行修改和处理。这在以下情况下非常有用:
- 添加统一的请求头信息:例如,你可以在每个请求中添加认证信息、token、用户身份等。
- 请求参数处理:你可以在这里对请求的参数进行处理,例如序列化数据,添加时间戳等。
- 验证请求体:可以在请求拦截器中验证请求体的合法性,以确保请求的有效性。
- 处理错误:你可以在这里处理请求发生的错误,例如网络连接问题或超时。
- 响应拦截器
同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
响应拦截器允许你在接收到响应之后对响应进行修改和处理。这在以下情况下非常有用:
- 数据统一处理:你可以在响应拦截器中处理返回的数据,例如将数据进行格式化、过滤或转换。
- 处理错误:你可以在响应拦截器中处理服务器返回的错误,例如判断登录失效、权限问题等。
- 全局loading效果:你可以在响应拦截器中控制全局的加载状态,比如显示和隐藏加载动画。
因此,拦截器是axios中很重要的一部分,正常编写网络请求代码时也离不开拦截器对请求和响应进行处理。所以我们在对axios进行二次封装时也要将拦截器合理封装进去。
封装代码
对axios二次封装后的项目结构如下
type.ts中主要对一些参数的类型进行了定义,用于ts的类型检测。HYRequestInterceptors类型中定义了请求拦截器和响应拦截器的函数类型。HYRequestConfig类型定义了参数config的类型。
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
export interface HYRequestInterceptors<T = AxiosResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: T) => T
responseInterceptorCatch?: (error: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYRequestInterceptors<T>
showLoading?: boolean
}
HYRequestInterceptors 接口:
HYRequestInterceptors 接口定义了请求拦截器和响应拦截器的配置。这里使用了泛型 T 来表示响应的数据类型,默认为 AxiosResponse。这个接口包含以下属性:
- requestInterceptor: 一个函数,用于对请求配置进行拦截和处理。该函数接收 AxiosRequestConfig 对象并返回相同类型的对象,允许你在发送请求之前修改请求的配置。
- requestInterceptorCatch: 一个函数,用于处理请求拦截器抛出的错误。该函数接收任意类型的错误,并返回相同类型的对象或值。
- responseInterceptor: 一个函数,用于对响应数据进行拦截和处理。该函数接收泛型 T 类型的响应数据,并返回相同类型的数据。允许你在接收到响应后修改响应的数据。
- responseInterceptorCatch: 一个函数,用于处理响应拦截器抛出的错误。该函数接收任意类型的错误,并返回相同类型的对象或值。
config.ts中主要对一些参数进行了定义,如baseURL,timeout等等
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = '/api'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
index.ts则定义了封装好的axios库。对于二次封装的axios库,对于拦截器的使用提供了不同粒度的用法。可以对单独某个实例定义拦截器,可以针对全体实例添加通用的拦截器,也可以针对实例的某次request请求来添加拦截器。
对于全体实例通用的拦截器,代码中主要添加了请求数据时的loading效果。从代码35行开始。 对于某一特定实例的拦截器,主要通过在config中添加,并在constructor中进行使用。24-31行代码就是对应的单独实例的拦截器。可以通过下图的方法来使用,在传入的参数中定义intetceptors。
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'
import { ElLoading } from 'element-plus'
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'
const DEAFULT_LOADING = true
class HYRequest {
instance: AxiosInstance
interceptors?: HYRequestInterceptors
showLoading: boolean
loading?: ILoadingInstance
constructor(config: HYRequestConfig) {
// 创建axios实例
this.instance = axios.create(config)
// 保存基本信息
this.showLoading = config.showLoading ?? DEAFULT_LOADING
this.interceptors = config.interceptors
// 使用拦截器
// 1.从config中取出的拦截器是对应的实例的拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 2.添加所有的实例都有的拦截器
this.instance.interceptors.request.use(
(config) => {
if (this.showLoading) {
this.loading = ElLoading.service({
lock: true,
text: '正在请求数据....',
background: 'rgba(0, 0, 0, 0.5)'
})
}
return config
},
(err) => {
return err
}
)
this.instance.interceptors.response.use(
(res) => {
// 将loading移除
this.loading?.close()
// 响应时仅返回res的data项
const data = res.data
if (data.returnCode === '-1001') {
console.log('请求失败~, 错误信息')
} else {
return data
}
},
(err) => {
// 将loading移除
this.loading?.close()
// 例子: 判断不同的HttpErrorCode显示不同的错误信息
if (err.response.status === 404) {
console.log('404的错误~')
}
return err
}
)
}
request<T = any>(config: HYRequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
config = config.interceptors.requestInterceptor(config)
}
// 2.判断是否需要显示loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
.request<any, T>(config)
.then((res) => {
// 1.单个请求对数据的处理
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 2.将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
// 3.将结果resolve返回出去
resolve(res)
})
.catch((err) => {
// 将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
reject(err)
return err
})
})
}
get<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
patch<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HYRequest
而对于定义好的实例,在进行某个请求时的单独拦截器使用,则如下图所示。在request中定义interceptors,这样定义的拦截器就是为这个具体的请求来使用的。