前言
Vue3的发布大肆推广了一波typescript,现在ts的使用也越来越广泛了。而axios作为目前最流行的http库,常驻于Vue、React和Angular三大前端框架开发的代码之中。在当前的开发环境中,ts与axios并肩作战的机会愈加频繁。本文主要就是介绍如何充分利用ts的特性简洁、高效地在代码中使用axios。
开始之前
本文会从零开始,由浅入深递进式地对axios进行封装,小伙伴可根据自身条件选择慢慢阅读或是直接跳到 完整代码。
接口准备
既然要使用axios,肯定需要准备相应的后端环境。自己手撸后端接口、使用Mock.js或者在线的Mock平台等都可以,这里就不详述该过程了,笔者用的是fastmock(在线平台),下面是接口相关内容:
// GET /api/success
{
"code": 0,
"message": "请求成功",
"data": {
"name": "管理员"
}
}
// GET /api/fail
{
"code": -1,
"message": "请求失败:XXX错误!",
"data": null
}
实验环境
首先对axios做一个最基础的封装,并编写调用上述两个测试接口的api,从js转型过来的小伙们都可以轻松地写出以下代码:
// @/utils/request.ts
import axios from 'axios'
const request = axios.create({
baseURL: '/api'
})
export default request
// @/api/test.ts
export const successApi = () => {
return request({
url: '/success',
method: 'get'
})
}
export const failApi = () => {
return request({
url: '/fail',
method: 'get'
})
}
最后简单的写下调用上述接口的页面,笔者用的是Vue3,这里用其它任意框架都行:
注:这里为简化代码,就不编写消息提示的组件,直接使用 console.log
与console.error
来替代
// App.tsx
import { successApi, failApi } from '@/api/test'
export default {
name: 'App',
setup () {
// 处理点击事件
const handleClick = async (isSuccess: boolean) => {
const api = isSuccess ? successApi : failApi
const res = await api()
if (res.data.code === 0) {
console.log(res.data.message) // 成功消息提示
console.log(res.data.data.name)
} else {
console.error(res.data.message) // 失败消息提示
}
}
// render 函数
return () => (
<div>
<button onClick={ () => handleClick(true) }>成功</button>
<button onClick={ () => handleClick(false) }>失败</button>
</div>
)
}
}
实验环境搭建完成,测试下两个接口的是通的就可以继续啦。
引入 Response 拦截器
上述代码实现了功能逻辑,但是却有两个明显的问题:
- 很多接口都会有消息提示,直接在组件中写消息提示会重复大量代码逻辑。
res.data.data.name
调用链太长,实际上,在组件中我们往往只会用到res.data中的部分。 这两个问题都可以通过引入axios自带的 Response 拦截器来解决, 如果用js的话,以下代码就解决问题了:
// @/utils/request.ts
request.interceptors.response.use((response) => {
const { data } = response
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
})
// App.tsx
import { successApi, failApi } from '@/api/test'
export default {
name: 'App',
setup () {
// 处理点击事件
const handleClick = async (isSuccess: boolean) => {
const api = isSuccess ? successApi : failApi
const data = await api()
if (data.code === 0) {
console.log(data.data.name)
}
}
// render 函数
return () => (
<div>
<button onClick={ () => handleClick(true) }>成功</button>
<button onClick={ () => handleClick(false) }>失败</button>
</div>
)
}
}
写完之后发现编译器报错了,原因是const data = await api()
中的data被编译器解析为AxiosResponse<any>
,而该类型中没有code这个属性,执行到if (data.code === 0)
这句时就会报错。看过axios源码或是对axios比较熟悉的小伙伴应该都知道AxiosResponse<any>
类型对应的是const { data } = response
中response的类型,在没有拦截器的时候的确返回的也是这个response。
总结一下原因:axios并不会根据传入的Response拦截器的函数类型去对自身的返回类型进行变动,所以当Response拦截器的返回类型不是AxiosResponse<any>
时就会出现类似的编译器问题。
简单粗暴的解决方案
对于这个问题,一行代码也能解决:
// App.tsx
const data: any = await api()
编译器不再报错,所有逻辑也正常进行。
自定义Response操作
但俗话说 一入any深似海,从此类型是路人,虽说目前data的类型的确是any,但是上述做法过于简单粗暴,不利于后续的深入封装,这里可采用自定义Response操作来解决:
// @/utils/request.ts
import axios, { AxiosRequestConfig } from 'axios'
const instance = axios.create({
baseURL: '/api'
})
const request = async (config: AxiosRequestConfig) => {
const { data } = await instance(config)
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
}
export default request
修改之后,可以将App.tsx中的any给去掉了:
// App.tsx
const data = await api()
OK,编译器不会再报错了。
使用泛型 - 返回类型声明
上面一通操作猛如虎,但是data的类型还是any,当我们对其进行操作时,也没有享受到ts带来的类型检查与只能提示,在我们与后端进行通信的时候,一般都会有固定的格式,所以我们可以再对返回类型进行声明:
// @/types/index.ts
interface MyResponseType {
code: number;
message: string;
data: any;
}
axios也为我们提供了一个非常友好的泛型方法:AxiosInstance.request
,可指定response.data的返回类型:
// @/utils/request
import { MyResponseType } from '@/types'
const request = async (config: AxiosRequestConfig): Promise<MyResponseType> => {
const { data } = await instance.request<MyResponseType>(config)
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
}
现在,在data上调用code、message等属性时已经能享受到ts带来的智能提示。如果需要与多个后端通信,也只需要相应地声明多个返回类型并编写对应的请求函数即可。
双层泛型 - 更进一步封装
经过上述操作,封装已经基本完善了,不过还是略有不足。在真实场景下,我们已经根据后端提供的接口文档,知道了接口/api/success
必定是返回一个带有name属性的类型,这个类型也是MyResponseType
中data属性的类型,但是我们在App.tsx中使用data.data时,依旧是一个any类型,不能有效地为我们生成name属性的智能提示。可以在发起请求的时候就声明返回结果中内层data的类型来解决这个问题
首先定义内层data类型(这里定义为User),并将MyResponseType
改为泛型类型:
// @/types/index.ts
export interface MyResponseType<T = any> {
code: number;
message: string;
data: T;
}
export interface User {
name: string;
}
然后将request函数也改造成泛型函数:
// @/utils/request
const request = async <T = any>(config: AxiosRequestConfig): Promise<MyResponseType<T>> => {
const { data } = await instance.request<MyResponseType<T>>(config)
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
}
在发起请求时指定泛型类型:
// @/api/test.ts
export const successApi = () => {
return request<User>({
url: '/success',
method: 'get'
})
}
export const failApi = () => {
return request<User>({
url: '/fail',
method: 'get'
})
}
回到App.tsx中,发现外层data被解析为MyResponseType<User>
类型,内层data被解析为User
类型,使用data.data.name
时,可以享受到智能提示了。
// App.tsx
const data = await api()
if (data.code === 0) {
console.log(data.data.name)
}
错误处理 - 最后的完善
ES6为我们带来的async await
语法可以帮助我们摆脱then catch
的多层回调地狱,不过执行异步操作的代码块还是需要包裹在try catch
块中,为了精简代码、减少嵌套,避免每次调用异步请求都用try catch
来进行包裹,笔者这里并没有使用try catch
块,而是直接通过Response中的code
属性来判断请求是否成功(0表示成功,-1表示失败),为了进一步减少代码嵌套可以这样修改(请求成功后往往都会有后续操作,请求失败已在自定义请求中进行消息提示,一般不会再有后续操作):
// App.tsx
const data = await api()
if (data.code !== 0) {
// 如果有请求失败的逻辑,在此执行
return
}
// 执行请求成功的逻辑
console.log(data.data.name)
不过如果完全不使用try catch
块的话,遇上错误码为4xx,5xx的错误,就无法捕获,且用户收不到消息提示,这样的体验显然是不好的,于是我们再在拦截器中对这些错误进行统一捕获并处理:
// @/utils/request
const request = async <T = any>(config: AxiosRequestConfig): Promise<MyResponseType<T>> => {
try {
const response = await instance(config)
const data: MyResponseType<T> = response.data
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
} catch (err) {
const message = err.message || '请求失败'
console.error(message) // 网络错误消息提示
return {
code: -1,
message,
data: null as any
}
}
}
注:在catch代码块中将data属性强制转化为any只是为了规避MyResponseType中data的类型检查,在请求已经报错的情况下,data中的内容不应该再被使用
完整代码
附上完整代码以供参考:
axios封装代码
// @/types/index.ts
export interface MyResponseType<T = any> {
code: number;
message: string;
data: T;
}
// @/utils/request.ts
import axios, { AxiosRequestConfig } from 'axios'
import { MyResponseType } from '@/types'
const instance = axios.create({
baseURL: '/api'
})
const request = async <T = any>(config: AxiosRequestConfig): Promise<MyResponseType<T>> => {
try {
const { data } = await instance.request<MyResponseType<T>>(config)
data.code === 0
? console.log(data.message) // 成功消息提示
: console.error(data.message) // 失败消息提示
return data
} catch (err) {
const message = err.message || '请求失败'
console.error(message) // 失败消息提示
return {
code: -1,
message,
data: null as any
}
}
}
export default request
使用示例
// @/types/index.ts
export interface User {
name: string;
}
// @/api/test.ts
import { User } from '@/types'
import request from '@/utils/request'
export const successApi = () => {
return request<User>({
url: '/success',
method: 'get'
})
}
export const failApi = () => {
return request<User>({
url: '/fail',
method: 'get'
})
}
深入学习
本文对axios的封装可以满足最常用的应用场景,如果业务场景需要更深层次的封装,笔者推荐参考下 vue-vben-admin,该仓库是一个基于Vue3+TS的后台管理系统模板,其对axios进行了深层次的封装。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!代办报建
本公司承接江浙沪报建代办施工许可证。
联系人:张经理,18321657689(微信同号)。
15条评论
写得实在太好了,我唯一能做的就是默默顶贴!http://huichengyu.com/news/zvza.html/
有钱、有房、有车,人人都想!http://90pu.millesimevoyagesdz.com
楼主很有艺术范!https://sdceda.com/shi/841747121/
祖国尚未统一,我却天天灌水,好内疚!http://test.cqyiyou.net/test/
有品位!https://sdceda.com/seo/294654573/
东方不败外加灭绝师太啊!http://j6mh.http://www.yngn123.com
有钱、有房、有车,人人都想!http://rrcxli.ccagtj-ford.com
看了这么多帖子,第一次看到这么有深度了!http://gk29l2.ywwzsh.com
在哪里跌倒,就在那里多爬一会儿!http://oe4xgf.bgrbz.com
读了楼主的帖子,顿时马桶就通了。。。http://k21r.xjwlht.com
顶一个!http://o3fo7.zuiyoulai.com
很经典,收藏了!http://www.3553km.com
这个帖子会火的,鉴定完毕!http://hcz.91c3.com
看了这么多帖子,第一次看到这么经典的!http://vr70.66aml.com
态度决定一切,不错!http://edmbnli.cn/html/94a98998916.html
发表评论