在线客服

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

adminadmin 报建百科 2024-04-25 114 14
ts 封装 axios 技巧:充分利用类型检查与提示

前言

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.logconsole.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 拦截器

上述代码实现了功能逻辑,但是却有两个明显的问题:

  1. 很多接口都会有消息提示,直接在组件中写消息提示会重复大量代码逻辑。
  2. 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(微信同号)。

喜欢0发布评论

14条评论

  • 访客 发表于 3个月前

    写得实在太好了,我唯一能做的就是默默顶贴!http://huichengyu.com/news/zvza.html/

  • 游客 发表于 2个月前

    有钱、有房、有车,人人都想!http://90pu.millesimevoyagesdz.com

  • 游客 发表于 2个月前

    楼主很有艺术范!https://sdceda.com/shi/841747121/

  • 游客 发表于 2个月前

    祖国尚未统一,我却天天灌水,好内疚!http://test.cqyiyou.net/test/

  • 游客 发表于 2个月前

    有品位!https://sdceda.com/seo/294654573/

  • 8001直播 发表于 2个月前

    东方不败外加灭绝师太啊!http://j6mh.http://www.yngn123.com

  • 一号棋牌娱乐 发表于 1个月前

    读了楼主的帖子,顿时马桶就通了。。。http://k21r.xjwlht.com

  • 游客 发表于 1个月前

    很经典,收藏了!http://www.3553km.com

发表评论

  • 昵称(必填)
  • 邮箱
  • 网址