ts 封装 axios 技巧:充分利用类型检查与提示

您所在的位置:网站首页 请求的定义 ts 封装 axios 技巧:充分利用类型检查与提示

ts 封装 axios 技巧:充分利用类型检查与提示

2023-10-06 18:21| 来源: 网络整理| 查看: 265

前言

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 () => ( handleClick(true) }>成功 handleClick(false) }>失败 ) } }

实验环境搭建完成,测试下两个接口的是通的就可以继续啦。

引入 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 () => ( handleClick(true) }>成功 handleClick(false) }>失败 ) } }

写完之后发现编译器报错了,原因是const data = await api()中的data被编译器解析为AxiosResponse,而该类型中没有code这个属性,执行到if (data.code === 0)这句时就会报错。看过axios源码或是对axios比较熟悉的小伙伴应该都知道AxiosResponse类型对应的是const { data } = response中response的类型,在没有拦截器的时候的确返回的也是这个response。

总结一下原因:axios并不会根据传入的Response拦截器的函数类型去对自身的返回类型进行变动,所以当Response拦截器的返回类型不是AxiosResponse时就会出现类似的编译器问题。

简单粗暴的解决方案

对于这个问题,一行代码也能解决:

// 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 => { const { data } = await instance.request(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 { code: number; message: string; data: T; } export interface User { name: string; }

然后将request函数也改造成泛型函数:

// @/utils/request const request = async (config: AxiosRequestConfig): Promise => { const { data } = await instance.request(config) data.code === 0 ? console.log(data.message) // 成功消息提示 : console.error(data.message) // 失败消息提示 return data }

在发起请求时指定泛型类型:

// @/api/test.ts export const successApi = () => { return request({ url: '/success', method: 'get' }) } export const failApi = () => { return request({ url: '/fail', method: 'get' }) }

回到App.tsx中,发现外层data被解析为MyResponseType类型,内层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 (config: AxiosRequestConfig): Promise => { try { const response = await instance(config) const data: MyResponseType = 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 { 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 (config: AxiosRequestConfig): Promise => { try { const { data } = await instance.request(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({ url: '/success', method: 'get' }) } export const failApi = () => { return request({ url: '/fail', method: 'get' }) } 深入学习

本文对axios的封装可以满足最常用的应用场景,如果业务场景需要更深层次的封装,笔者推荐参考下 vue-vben-admin,该仓库是一个基于Vue3+TS的后台管理系统模板,其对axios进行了深层次的封装。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3