前言
Axios 也是用的比较多的一个库了,其属于 XMLHttpRequests 类型请求,与 fetch 不一样,XMLHttpRequests比较友好的是,支持上传下载进度条,而 fetch 不支持,因此也有不少人更倾向于,基于 XMLHttpRequests 的 Axio(基于 fetch 就是前面用的 umi-request 了)
也可以理解为,如果原生不支持的基础功能,基本上三方组件也不支持了
安装 axios
安装 axios 库
yarn add axios
//npm install axios
//pnpm add axios
axios 基础使用与拦截
基础使用
axios 可以直接使用,也可以使用 .create 创建,还有其他参数,则可以点击进去查看,这里不多介绍了,.get .post后面也是可以直接跟 url 等信息的
import axios from 'axios'
axios.get(url)
//我们可以创建一个更独立的axios对象,方便我们特殊时候使用多套基础配置
export const instance = axios.create({
baseURL: '',
timeout: 15000,
})
instance.get(url, data)
请求拦截
请求拦截(.interceptors.request.use),一般处理身份认证 token 使用,也有隐晦放置一些参数处理的
//拦截器处理请求体 request
instance.interceptors.request.use((config) => {
//可以做token处理,也可以做其他的
config = handleAuth(config)
return config
})
const handleAuth = <T>(config: T) => {
//处理token,当然 token第一次获取到保存起来就行,没必要每次都 getItem
token = token || localStorage.getItem('token') || ''
return {
...config,
'X-Auth-Token': token,
// 'token': token,
}
}
响应拦截
响应拦截(.interceptors.response.use),我们一般统一处理数据,或者做前面一些文章提到的 双 token 无感刷新,这里就做一个无感刷新吧,对于参数处理,后面在介绍,有不同的思路
//拦截器处理响应体 response,我们顺便做一个无感刷新的
api.interceptors.response.use(
(response) => response,
async (error) => {
const { data, config } = error.response
if (data.statusCode === 401 && !config.url.includes('/refresh')) {
const res = await refreshToken()
//使用config重发
if (res.status === 200) {
return api(config)
} else {
alert(data.message || '登录过期,请重新登录')
}
} else {
//如果有需要处理其他的也可以直接罗列,非正常的错误,都不给用户展示,可以直接打印
return error.response
}
},
)
const refreshToken = async () => {
const res = await api.get('/refresh', {
params: {
token: localStorage.getItem('refresh_token'),
},
})
localStorage.setItem('token', res.data.accessToken)
localStorage.setItem('refresh_token', res.data.refreshToken)
return res
}
封装方案一(返回数据和错误信息同时返回)
这种方案也是以前看过的一篇文章写到的,主要是将返回结果、错误结果,放置到一个元组中,同时返回,如果没有错误信息,则成功,有错误信息看情况处理错误信息即可
这种方案个人也是比较喜欢的,能够节省不少代码,并且大家代码开发思路结构一样,也很舒服,缺点也很明显,不是很灵活,比较适合后端规范性比较强的情况
封装get请求
我们直接使用 axios 或者 自己创建一个 axios 对象即可,直接调用其 get 方法,然后无论成功或者失败都是 resolve,这样就不会走 .catch 了,直接以元组的方式,同时返回错误和结果,没问题时,错误返回 null 即可,可以通过 err 参数判断是否正确
ps:一般元组返回为 [err, res],也就是错误信息和正确数据,可能是一般要优先判断 err 的原因吧
import { api } from '.'
export const get = <T = unknown>(
url: string,
params: Record<string, unknown> = {},
options: AxiosRequestConfig<unknown> = {},
): Promise<[ResponseType<T> | null, unknown]> => {
return new Promise((resolve) => {
api
.get(url, { params, ...options })
.then((result) => {
const res: ResponseType<T> = result.data
if (res.code === 200) {
resolve([null, res])
} else if (res.code === -1) {
//这种一般是有效,可以直接提示,此时res就是错误err了
resolve([res, null])
} else {
//仍然是错误的,需要提示,这种一般统一提示,此时res就是错误err了
resolve([res, null])
}
})
.catch((err) => {
//仍然是错误的,需要提示,这种一般统一提示
resolve([err, null])
})
})
}
使用如下所示
const [err, res] = await get('userinfo')
if (!err) {
//这就是正确的结果
}
//实际上错误,可以这里处理,也可以统一弹窗处理
封装post(带常见的form、json类型参数)
而 post 使用还是不太一样,因为我们平时开发过程中,不同的人使用的 Content-Type 他可能不一样,这里 umi-request 分成了两个 form、json,当然 json 也是最常见的(我们开始的开发就是 form)
form: Content-Type: application/x-www-form-urlencoded;json: Content-Type: application/json;
我们需要添加一个 requestType 参数,用于统一处理我们的 post请求 的 content-type 类型
import axios, { type AxiosRequestConfig } from 'axios'
import { api } from '.'
//目前就前两种主流 Content-Type:application/x-www-form-urlencoded; Content-Type:application/json;
//上传文件一般不用通用接口,忽略
export type RequestType = 'form' | 'json'
//设置默认类型为 json
const defaultRequestType = 'json'
type ResponseType<T = unknown> = {
code?: number
data?: T
msg?: string
}
//分装请求,逻辑和 get 差不多,只不过加入了 Content-Type 的处理
export const post = <T = unknown>(
url: string,
data: Record<string, unknown> = {},
options: AxiosRequestConfig = {},
requestType: RequestType = defaultRequestType,
): Promise<[ResponseType<T> | null, unknown]> => {
return new Promise((resolve) => {
const headers = options.headers || {}
if (requestType === 'json') {
headers['Content-Type'] = 'application/json'
} else if (requestType === 'form') {
headers['Content-Type'] = 'application/x-www-form-urlencoded'
}
api
.post(url, data, {
...options,
headers,
})
.then((result) => {
const res: ResponseType<T> = result.data
if (res.code === 200) {
resolve([null, res])
} else if (res.code === -1) {
//这种一般是有效,可以直接提示
resolve([null, res])
} else {
//仍然是错误的,需要提示,这种一般统一提示
resolve([null, res])
}
})
.catch((err) => {
//仍然是错误的,需要提示,这种一般统一提示
resolve([null, err])
})
})
}
upload上传
upload上传实际上走的也是 post,只不过是 form-data 数据罢了,其对应的 content-type也不一样,为 Content-Type: multipart/form-data,我们单独使用即可,一般不会走我们的通用业务接口,大多是走的云服务器,基本不需要额外配置参数(可能需要配置 timeout 哈)
///options.onUploadProgress进度条
export const upload = (url: string, data: FormData, options: AxiosRequestConfig = {}) => {
// Content-Type:multipart/form-data
return axios.post(url, data, {
headers: {
'Content-Type': 'multipart/form-data',
},
...options,
})
}
封装方案二(放弃细节封装)
这个方案比较简单,就是我们放弃我们上面的封装格式,直接使用默认的返回结果即可
为什么这么写,看后面的图
import type { AxiosRequestConfig } from 'axios'
import { api } from ".";
//可以写成这样,为何写成这样,可以看后面的图
export const request = <T>(url: string, options: AxiosRequestConfig) => {
return api.request<T>({
url,
...options,
})
}
//当然我们可以再改装一下,可以进一步处理数据,也可以不处理
export const request = <T>(url: string, options: AxiosRequestConfig) => {
return new Promise((resolve, reject) => {
api
.request<T>({
url,
...options,
})
.then((result) => {
resolve(result.data)
})
.catch(reject)
})
}
//如果很多接口非常统一,那么我们在改进一下,特殊情况使用,这也是项目统一时,本人比较懒的使用方式
//单独使用可以额外加上一个静默通用也行,不一定使用 silent 0 不静默都提示,1仅仅提示有效,2全都不提示
export const request = <T extends ResponseType<T>>(
url: string,
options: AxiosRequestConfig,
//silent = 0,
): Promise<ResponseType<T> | undefined> => {
return new Promise((resolve, reject) => {
api
.request<T>({
url,
...options,
})
.then((result) => {
const res: ResponseType<T> = result.data
if (res.code === 200) {
resolve(res)
return
} else if (res.code === -1) {
//可以做一个提示
return Promise.reject(res)
} else {
return Promise.reject()
}
})
.catch((err) => {
//存在有效提示就有效提示,不存在且静默,那就不提示
//if (silent === 2) {
//return
//}
const msg = err?.msg || '似乎断开了与网络的连接'
alert(msg)
reject(msg)
})
})
}
下面的图,是之前使用 umi-request 的时候,找到的 openapi 自动生成的接口数据,只要 swagger 文档写好了,那么直接一键生成,很方便,为了保证格式一致,直接封装成上面那样子,参考这里
实际上在一些项目多的情况下,这种方案能节省不少时间,即使是方案一,实际上也就是节省部分代码或者规范部分判断,实际上并没有节省太多
最后
就介绍到这里吧,实际使用也挺简单的,当然直接用也没毛病
对于统一处理的情况,实际上个人觉得,第一种挺好用的,但是还不够懒,第二种懒得就可以了,openapi + 统一处理,懒到极点,挺好
ps:再懒请求头的 token 还是要处理的,毕竟再懒也不能完不成功能😂
第一种,偏向于传统,需要处理的东西少,使用也比较简单,仍然要写判断和处理罢了😂