赞
踩
每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
Promise本身是可以有类型的, 对resolve,reject参数类型限定
new Promise<string>((resolve, reject) => {
// resolve(321) 报错: number类型
resolve('james') //字符串类型
}).then(res => {
console.log(res) //这里的res就是字符串的形式
})
以get为例
get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
一:参数
get()
是一个函数,接收两个参数,返回一个Promise对象
AxiosRequestConfig
, 看下面二:返回值的类型
上面解释了Promise的对象,本身是有类型的,类型为 R
R是卅呢? 泛型 R = AxiosResponse<T>
, 泛型接口, AxiosResponse
,看下面
T
也是泛型, 看了AxiosResponse的接口之后,很明显知道 T 就是data的类型
1、对象配置的类型
export interface AxiosRequestConfig {
url?: string;
method?: Method;
baseURL?: string;
headers?: any;
params?: any;
data?: any;
timeout?: number;
withCredentials?: boolean;
responseType?: ResponseType;
...
}
2、axios的返回值类型
export interface AxiosResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request?: any;
}
3、实例
//http.get()返回一个promise对象, promise返回值的类型为 string[]
http.get<string[]>('http://httpbin.org/get').then(res => {
// res.data: string[]
})
在这里主要是使用ES6提供的类来对axios进行封装,在使用的时候,通过实例来进行使用。
封装类:
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
class Http {
public instance: AxiosInstance
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
}
}
export default Http
可以根据类来进行创建实例:
import Http from './service/index'
//配置基本路径和请求时间 (当然这里也可以写在配置文件)
const BASE_URL = 'http://127.0.0.1'
const TIME_OUT = 1000
const http = new Http({
baseURL: BASE_URL,
timeout: TIME_OUT
})
export default http
拦截器主要分为以下三种的形式,但是主要的形式,第一种和第三种是不常见的(了解即可),最主要的还是同统一的拦截器(掌握)
针对不同实例之间的进行不同的拦截(了解)
对所有实例进行统一拦截(掌握)
针对不同接口进行不同的拦截(了解)
尽管上面三种情况,两种需要了解,但是还是需要理解他的封装原理
先对ts接口进行实现(接下来会使用到)
//自定义拦截器的类型限定(针对于第一种情况和第三种情况)
interface customInterceptorType {
//请求拦截(接口请求成功)
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any //请求拦截(捕获接口失败)
resInterceptor?: (res: AxiosResponse) => AxiosResponse //响应拦截(接口响应成功)
resInterceptorCatch?: (error: any) => any //响应拦截(捕获响应失败)
}
//自定义的axios的config的配置(使用接口继承)
interface customRequest extends AxiosRequestConfig {
interceptor?: customInterceptorType //是否传入拦截器
}
对axios中的config的类型,进行重写
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
class Http {
public instance: AxiosInstance
constructor(config: customRequest) { //使用自定义的axios的config的配置
this.instance = axios.create(config)
}
}
export default Http
不同的实例,其实就是存在两个项目之间。(主要是为了让封装更加的具有扩展性)
使用class封装的axios,通过new关键字进行实例化,来进行使用的。那么创建实例就会存在多个实例,那么他们之间的拦截可能是想要不一样的功能。
样例:
假如存在两个实例, http1
和 http2
, http1
发送请求之前,在url前面加上专属的字段,如:/http1/getSearchData
; http2
在发送之前修改url加上专属的字段,如: http2/getSearchData
, 当然这种情况很少见,这里只是简单的举个例子
类实例初始化:
import Http from './request/request' const http1 = new Http({ baseURL: BASE_URL, timeout: TIME_OUT, interceptor: { requestInterceptor: (config) => { config.url = '/http1' + config.url return config }, //其他的三种拦截器可选 } }) const http2 = new Http({ baseURL: BASE_URL, timeout: TIME_OUT, interceptor: { requestInterceptor: (config) => { config.url = '/http2' + config.url return config }, //其他的三种拦截器可选 } })
在上面的代码中,在初始化的时候, 就传入了一个请求拦截器,对url字符串的修改,这样就达到了,对不同的实例之间的不同拦截。
类中处理实例传入的拦截器
class Http { public instance: AxiosInstance public interceptor?: customInterceptorType constructor(config: customRequest) { this.instance = axios.create(config) this.interceptor = config.interceptor //如果存在this.interceptor, 就处理实例中传递过来的拦截器 this.instance.interceptor.request.use( this.interceptor?.requestInterceptor, this.interceptor?.requestInterceptorCatch ) this.instance.interceptors.response.use( this.interceptor?.resInterceptor, this.interceptor?.resInterceptorCatch ) } } export default Http
上面代码,就单独处理实例传递过来的拦截器,注入。
这个是非常常用的拦截器,这里可以大部分想要的实现功能拦截,所以我认为这是必须要掌握的。比如:
当然,还有很多的情况处理。这样的代码逻辑,也是比较好处理的
class Http { public instance: AxiosInstance ... this.instance.interceptors.request.use( (config) => { //请求拦截:对token的拦截 const token = localStorage.getItem('token') token && config.headers.common['token'] = token return config }, (error) => { return error } ) this.instance.interceptors.response.use( (res) => { //响应拦截: 接口返回的数据处理 return res.data }, (error) => { return error } ) }
在某些场景下,需要对个别接口进行单独的拦截处理。那么在封装类中,我们就需要单独对这种拦截器处理。
样例:
针对大部分的接口,请求超时的时间时统一的,但是对于处于复杂的接口,请求的时间肯定会长一些。比如登陆接口
,如果时间超出2s,那么就说明请求接口失败。但是如果针对于人脸识别的接口
,如果时间在1分钟左右,我们也是可以接受的。所以这种情况下,就需要对接口拦截器进行处理。
类中处理接口拦截器
class Http {
request(config: customRequest): any {
if (config.interceptor?.requestInterceptor) {
//调用接口传递过来的拦截器函数,接收最新的config
config = config.interceptor.requestInterceptor(config)
}
this.instance.request(config).then((res) => {
if (config.interceptor?.resInterceptor) {
config = config.interceptor.resInterceptor(res)
}
})
}
}
接口的使用拦截器:
import http from './service/index' //登陆接口 http.request({ url: '/login', method: 'POST', interceptor: { requestInterceptor: config => { config.timeout = 2000 return config }, //其他三种也可以写的 } }) //人脸识别接口 http.request({ url: '/recognitionFace method: 'POST', interceptor: { requestInterceptor: config => { config.timeout = 60000 return config }, //其他三种也可以写的 } })
这里就单独对接口的超时时间进行了处理,当然还有其他的应用场景。
在上面的类中处理接口拦截器这个代码中,有个bug,就会类型会报错,因为我们一般会在统一接口中,在响应拦截的时候,就返回了res.data
,这个类型我们传入过去的promise类型了,但是在resInterceptor
这个接口函数中类型为:AxiosResponse
,所以要想处理这个问题(就简单粗暴解决吧),改为any类型
interface customInterceptorType {
//请求拦截(接口请求成功)
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any //请求拦截(捕获接口失败)
resInterceptor?: (res: any) => any //响应拦截(接口响应成功)
resInterceptorCatch?: (error: any) => any //响应拦截(捕获响应失败)
}
小节总结:上面的拦截情况分类三种,但是第一种和第三种,我们只需要了解,然后扩宽我们的封装思路,但是第二种的统一处理,是必须要掌握,使用起来也很简单。
在请求接口的时候,想要用户体验感好的话,就需要给用户的提示,所以需要加上一个loading图标来提示用户,说明接口真正请求中。
这里我主要使用element-plus中的加载动画。
实现的效果:
http.request({
url: '/get',
method: 'GET'
}).then((res:any) => {
console.log(res);
})
//默认情况下,就是有loading图标
http.request({
url: '/get',
method: 'GET',
showLoading: false
}).then((res:any) => {
console.log(res);
})
//设置showLoading为false, 就不展示loading图标
封装代码如下:
类型接口:
interface customRequest extends AxiosRequestConfig {
interceptor?: customInterceptorType,
showLoading?: boolean //config配置文件的类型上,添加一个showLoading属性,展示是否需要展示
}
//导入ElLoading的动画组件 import { ElLoading } from 'element-plus' //导入ElLoading中的service返回值的类型 import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type' const DEFAULT_LOADING = true //loading 的默认值 class Http { protected instance: AxiosInstance interceptor?: customInterceptorType loading?: ILoadingInstance //定义loading showLoading?: boolean //是否显示loading constructor(config: customRequest) { this.instance = axios.create(config) this.interceptor = config?.interceptor //初始化默认值,如果config里面有,就使用config里面的,但是如果没有就使用默认值 this.showLoading = config.showLoading || DEFAULT_LOADING this.instance.interceptors.request.use( (config) => { //loading图标加载 if(this.showLoading) { //当为true的时候,显示loading图标 this.loading = ElLoading.service({ lock: true, text: '正在加载中...' }) } return config }, (error) => { return error } ) this.instance.interceptors.response.use( (res) => { //当响应成功的时候,关闭loading setTimeout(() => { this.loading?.close() }, 1000) return res.data }, (error) => { //当响应失败的时候,关闭loading this.loading?.close() return error } ) } //request方法 request(config: customRequest): any { return new Promise((resolve, reject) => { //单独处理接口配置传递showLoading, 为false就是显示图标 if(config.showLoading === false) { this.showLoading = false } //调用接口之后,无论成功还是失败,都需要把实例的showLoading改为true this.instance.request(config).then((res) => { resolve(res) //为什么要true, 是为了不影响下一次的接口的loading加载动画 this.showLoading = DEFAULT_LOADING }).cathc((err) => { this.showLoading = DEFAULT_LOADING return err }) }) } } export default Http
对request方法详细说明
request<T>(config: customRequest): Promise<T> {
return new Promise((resolve) => {
//这里的instance中的request,看源码知道,这里接收两个泛型,第二个泛型才是Promise返回的对象
this.instance.request<any, T>(config).then((res) => {
resolve(res) //所以这里res的类型就为 T 了
})
})
}
在上面的代码中,request接收一个泛型,该方法返回一个Promise
对象,这个泛型就是对象Promise的类型设定,对最后的返回执行进行设置。
对其他的方法封装:
get<T>(config: customRequest): Promise<T>{
return this.request({...config, method: 'GET'})
}
post<T>(config: customRequest): Promise<T>{
return this.request({...config, method: 'POST'})
}
使用封装的方法时
interface IResType {
data: any,
status: number,
success: boolean
}
http.get<IResType>({
url: '/get'
}).then(res => {
console.log(res);
})
在上面的代码中,data为any
,但是我们想对data中的返回类型进行限定,那么该怎么处理呢?
interface IResType<T> {
data: T,
status: number,
success: boolean
}
http.request<IResType<any>>({
method: 'GET',
url: '/get'
}).then(res => {
console.log(res);
})
当然,要明确知道data的类型, 必须要跟后端统一,不然一方改变就会报错的,接口类型行不通
import axios from 'axios' import type { AxiosInstance, AxiosRequestConfig } from 'axios' import { ElLoading } from 'element-plus' import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type' //自定义拦截器类型 interface customInterceptorType { requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig requestInterceptorCatch?: (error: any) => any resInterceptor?: (res: any) => any resInterceptorCatch?: (error: any) => any } //定义自己的实例类型 interface customRequest extends AxiosRequestConfig { interceptor?: customInterceptorType, showLoading?: boolean } const DEFAULT_LOADING = true //loading 的默认值 class Http { protected instance: AxiosInstance interceptor?: customInterceptorType loading?: ILoadingInstance showLoading: boolean constructor(config: customRequest) { this.instance = axios.create(config) this.interceptor = config?.interceptor this.showLoading = config.showLoading || DEFAULT_LOADING //从config里面取出每个实例的不同拦截器 (可以删除,如果没有单独实例处理) this.instance.interceptors.request.use( this.interceptor?.requestInterceptor, this.interceptor?.requestInterceptorCatch ) this.instance.interceptors.response.use( this.interceptor?.resInterceptor, this.interceptor?.resInterceptorCatch ) //统一,所以实例都会拦截 this.instance.interceptors.request.use( (config) => { //loading图标加载 if(this.showLoading) { this.loading = ElLoading.service({ lock: true, text: '正在加载中...' }) } //token拦截 const token = localStorage.getItem('token') if(token) { config.headers.Authorization = token } return config }, (error) => { return error } ) this.instance.interceptors.response.use( (res) => { //当响应成功的时候,关闭loading setTimeout(() => { this.loading?.close() }, 1000) return res.data }, (error) => { //当响应失败的时候,关闭loading this.loading?.close() return error } ) } // request<T>(params: customRequest): Promise<T> // request<T>(...args: any[]): Promise<T> //对部分接口的进行拦截配置 request<T>(params: customRequest): Promise<T> { let config:customRequest if(typeof params === 'string') { //字符串 // config = arguments[1] || {} // config.url = arguments[0] //处理url // if(arguments[2]) { // config.showLoading = arguments[2] || {} //处理showLoading // } } else { //对象 config = params || {} } return new Promise((resolve) => { if (config.interceptor?.requestInterceptor) { //可以删除,没有接口拦截器 config = config.interceptor.requestInterceptor(config) } if(config.showLoading === false) { this.showLoading = false } this.instance.request<any, T>(config).then((res) => { if (config.interceptor?.resInterceptor) { //可以删除,没有接口拦截器 config = config.interceptor.resInterceptor(res) } resolve(res) this.showLoading = DEFAULT_LOADING }) }) } get<T>(config: customRequest): Promise<T>{ return this.request({...config, method: 'GET'}) } post<T>(config: customRequest): Promise<T>{ return this.request({...config, method: 'POST'}) } } export default Http
这里的代码唯一美中不足的就是,get等方法还不支持传递字符串形式,只能以对象的形式作为参数。因为目前我不知道怎么在ts使用剩余运算符,直接使用arguments,好像直接报错;第二次测试,打算用函数的重载,但是好像也是需要使用剩余参数,还没解决出来。如果以后有思路了,再来进行补充(脚步是一个一个的走的)。
在这次二次封装axios的过程中,用到了太多的ts的类型限定了,各种泛型的使用,对Promise的类型全面认识,大大的加强了我对ts的使用,对ts的安全检测类型有着全面的认识;而且这次还加强了自己的封装能力,让自己封装的函数,更加的具有扩展性,安全性,感觉这次真的收获巨大。
如果其中有什么不对地方,可以提出来,虚心接受(一位菜鸟小白的成长之路)。
最后想说,站在大佬(coderwhy老师)的肩膀上学习,一切不可能就能变为可能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。