ts封装axios最佳实践示例详解
作者:可畏 时间:2024-04-19 09:59:07
简介
🤔看了一圈,大家对 ts
封装 axios
都各有见解。但都不是我满意的吧,所以自己封装了一个💪。至于为什么敢叫最佳实践,因为我满意,就这么简单粗暴🏋🏻。
什么样封装才是最合理的
别再用 promise
包了,好吗?
看了一下,很多人封装 axios
的时候都用 promise
包装了一层,甚至更有甚者用起了 try catch
。为什么反对用 promise
包装,因为 axios
返回的就是个 promise
,脱裤子放屁,完全没必要🧔‍♀️。至于 try catch
这个是用于捕获未知错误的,比如 JSON.parse
的时候,有些字符串就是无法转换。记住一句话,滥用 try catch
和随地大小便没有区别。
一个 request
方法 * ,噗!我一口老血🥵
部分人直接就一个 request
方法 * ,所有参数与配置都写在一起,看起来一点也不清晰,简洁。请求有多种方式,get
,post
,put...
,最合理的请求方式应该是 instance[method](url, data, options)
。对应 请求地址、请求参数、请求配置项,一目了然。
扩展我需要的请求,不要再 ts-ignore
了🤬
如果 ts-ignore
用多了,就会产生依赖性。不排除情况紧急急着上线,或者 类型处理 复杂的,但是在有时间的时候,还是得优化一下,作为程序员,追求优雅,永不过时。
求你了!把 * 拿出来吧😠
封装的时候我们都会封装一个请求类,但对应 * 应该解耦出来。因为每个域名的 * 处理可能不一致,写死的话封装请求类的意义也就没有了。
接口请求 then
里面又判断后端返回码判断请求是否成功,太狗血了!😞
🧑‍🏫看到下面这种代码,给我难受的啊。
api.post(url, data).then((res) => {
if (res.code === 1) {
// ...
} else {
// 全局消息提示
console.error(res.message)
}
})
既然是一个 promise
,我们就应该知道 promise
只有成功或者失败。then
怎么会有成功错误的处理呢?then
里面就是请求成功,没有什么 if else
,处理失败去 catch
里面处理去。这么喜欢写 if else
,你是没写过单元测试是吧?
开整
OK
,吐槽了这么多,这时候肯定就有人说了,光说谁不会啊,你整一个啊!🤐
瞧你这话说的,一点活没干,还让你白嫖了。你咋这么能呢🙄?
不过话说回来,我不要活在他人的评价里,我做这件事情不是因为你的讽刺或者吹捧,而是我自己要做🧑‍🦱。
接下来定一下要做的事情
封装一个请求类
适当扩展
axios
类型,为自定义配置打地基支持自定义请求配置。如是否全局错误提示
* 单独拎出来,方便扩展新的请求
开整之前先看看 axios 基本类型
// 这是 axios 请求类型定义,但是因为我们需要支持自定义配置,所以待会需要把它拓展一下
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders;
params?: any;
paramsSerializer?: ParamsSerializerOptions;
data?: D;
timeout?: Milliseconds;
timeoutErrorMessage?: string;
withCredentials?: boolean;
// ...
}
// 这是 axios 请求返回类型定义,里面类型需要处理,所以这个我们也得处理一下。
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
// 这里的配置没有支持拓展,所以待会也得处理一下
config: InternalAxiosRequestConfig<D>;
request?: any;
}
// 所以我们只需要改造 3 个 axios 类型定义就行了
// 另外我们需要定义下自己的 * 和 请求结果封装
Talk is cheap,show me the code.
代码也不多,就也不多解释了,基本注释都加上了。下面是全部代码。
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
// 定义一个常见后端请求返回
type BaseApiResponse<T> = {
code: number
message: string
result: T
}
// 拓展 axios 请求配置,加入我们自己的配置
interface RequestOptions {
// 是否全局展示请求 错误信息
globalErrorMessage?: boolean
// 是否全局展示请求 成功信息
globalSuccessMessage?: boolean
}
// 拓展自定义请求配置
interface ExpandAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
interceptorHooks?: InterceptorHooks
requestOptions?: RequestOptions
}
// 拓展 axios 请求配置
interface ExpandInternalAxiosRequestConfig<D = any> extends InternalAxiosRequestConfig<D> {
interceptorHooks?: InterceptorHooks
requestOptions?: RequestOptions
}
// 拓展 axios 返回配置
interface ExpandAxiosResponse<T = any, D = any> extends AxiosResponse<T, D> {
config: ExpandInternalAxiosRequestConfig<D>
}
export interface InterceptorHooks {
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>
responseInterceptorCatch?: (error: any) => any
}
// 导出Request类,可以用来自定义传递配置来创建实例
export default class Request {
// axios 实例
private _instance: AxiosInstance
// 默认配置
private _defaultConfig: ExpandAxiosRequestConfig = {
baseURL: '/api',
timeout: 5000,
requestOptions: {
globalErrorMessage: true,
globalSuccessMessage: false
}
}
private _interceptorHooks?: InterceptorHooks
constructor(config: ExpandAxiosRequestConfig) {
// 使用axios.create创建axios实例
this._instance = axios.create(Object.assign(this._defaultConfig, config))
this._interceptorHooks = config.interceptorHooks
this.setupInterceptors()
}
// 通用拦截,在初始化时就进行注册和运行,对基础属性进行处理
private setupInterceptors() {
this._instance.interceptors.request.use(this._interceptorHooks?.requestInterceptor, this._interceptorHooks?.requestInterceptorCatch)
this._instance.interceptors.response.use(this._interceptorHooks?.responseInterceptor, this._interceptorHooks?.responseInterceptorCatch)
}
// 定义核心请求
public request(config: ExpandAxiosRequestConfig): Promise<AxiosResponse> {
// !!!⚠️ 注意:axios 已经将请求使用 promise 封装过了
// 这里直接返回,不需要我们再使用 promise 封装一层
return this._instance.request(config)
}
public get<T = any>(url: string, config?: ExpandAxiosRequestConfig): Promise<AxiosResponse<BaseApiResponse<T>>> {
return this._instance.get(url, config)
}
public post<T = any>(url: string, data?: any, config?: ExpandAxiosRequestConfig): Promise<T> {
return this._instance.post(url, data, config)
}
public put<T = any>(url: string, data?: any, config?: ExpandAxiosRequestConfig): Promise<T> {
return this._instance.put(url, data, config)
}
public delete<T = any>(url: string, config?: ExpandAxiosRequestConfig): Promise<T> {
return this._instance.delete(url, config)
}
}
以及使用的 demo
。这个保姆级服务满意吗?
// 请求 *
const transform: InterceptorHooks = {
requestInterceptor(config) {
// 请求头部处理,如添加 token
const token = 'token-value'
if (token) {
config!.headers!.Authorization = token
}
return config
},
requestInterceptorCatch(err) {
// 请求错误,这里可以用全局提示框进行提示
return Promise.reject(err)
},
responseInterceptor(result) {
// 因为 axios 返回不支持扩展自定义配置,需要自己断言一下
const res = result as ExpandAxiosResponse
// 与后端约定的请求成功码
const SUCCESS_CODE = 1
if (res.status !== 200) return Promise.reject(res)
if (res.data.code !== SUCCESS_CODE) {
if (res.config.requestOptions?.globalErrorMessage) {
// 这里全局提示错误
console.error(res.data.message)
}
return Promise.reject(res.data)
}
if (res.config.requestOptions?.globalSuccessMessage) {
// 这里全局提示请求成功
console.log(res.data.message)
}
// 请求返回值,建议将 返回值 进行解构
return res.data.result
},
responseInterceptorCatch(err) {
// 这里用来处理 http 常见错误,进行全局提示
const mapErrorStatus = new Map([
[400, '请求方式错误'],
[401, '请重新登录'],
[403, '拒绝访问'],
[404, '请求地址有误'],
[500, '服务器出错']
])
const message = mapErrorStatus.get(err.response.status) || '请求出错,请稍后再试'
// 此处全局报错
console.error(message)
return Promise.reject(err.response)
}
}
// 具体使用时先实例一个请求对象
const request = new Request({
baseURL: '/api',
timeout: 5000,
interceptorHooks: transform
})
// 定义请求返回
interface ResModel {
str: string
num: number
}
// 发起请求
request
.post<ResModel>(
'/abc',
{
a: 'aa',
b: 'bb'
},
{
requestOptions: {
globalErrorMessage: true
}
}
)
.then((res) => {
console.log('res: ', res)
console.log(res.str)
})
可以看到鼠标浮上去就能看到定义了,完美!
最后源码地址: github.com/coveychen95…
来源:https://juejin.cn/post/7208465670992478269