Axios的封装思路与技巧

Axios的封装思路与技巧

提示:文中使用的为ts代码,对ts不熟悉的同学可以删除所有类型降级为js代码,不影响使用

前言

项目中或多或少会有一些需要接口发送请求的需求,与其复制粘贴别人在业务中对请求方法的使用,不如自己花点时间研究项目中请求方法的实现,这样在处理请求出现的问题时能够更好的定位问题原因。本文循序渐进的介绍了如何对axios进行封装实现自己项目中的请求方法,希望各位同学在阅读后能有一定的体会,如有问题还请大家在评论区指正。

1.创建axios实例

创建一个axios实例,在这里为实例进行一些配置(如超时时间)。但是要注意,不要在此处配置一些动态的属性,如headers中的token,具体的原因我们会在后面提起

javascript 复制代码
import axios from 'axios'
const instance = axios.create({
  timeout: 1000 * 120, // 超时时间120s
})

为实例配置拦截器(请求拦截器,响应拦截器)

可能有些同学对axios不太熟悉,不了解请求拦截器和响应拦截器的作用,这里会简单介绍一下

请求拦截器:在请求发送前进行拦截,或者对请求错误进行拦截

javascript 复制代码
instance.interceptors.request.use(
  config => {
    // config为AxiosRequestConfig的一个实例,它是包含请求配置参数的对象
    // 在这里可以在请求发送前做一些处理,如向config实例中添加属性,取消请求,设置loading等  
    return config
  },
  // 这里是请求报错时的拦截方法,这里直接返回一个状态为reject的promise
  // 实际测试时,即使前端请求报错并且未到达后端,也没有触发这里的钩子函数
  error => Promise.reject(error),
)

响应拦截器:在响应被.then.catch处理前拦截

javascript 复制代码
instance.interceptors.response.use(
  response => {
    // 响应成功的场景
    // 在这里可以关闭loading或者对响应的返参对象response进行处理
    return response
  },
  error => {
    // 响应失败的场景
    // http状态码不为2xx时就会进入,根据项目要求处理接口401,404,500等情况
    // 返回的promise也可以根据项目要求进行修改
    return Promise.reject(error)
  },
)

这样我们就创建了一个可用的axios实例,对于实例的一些其他配置可以参考axios官网

2.创建Abstract类进一步封装

在创建了一个axios实例之后,我们就可以使用它去发送请求了,但是出于减少重复代码的目的,我们不在业务代码中直接使用axios实例去发送各种请求,而是选择去做进一步的封装让整体的代码更加简洁

通常来说,我在项目中更喜欢用面向对象的方式去对axios做进一步的封装,使用这种方式的优点会在后面进行说明 创建一个类,起名可以按自己的喜好来,这里我写的是Abstract,因为它的主要作用是做为一个底层的类让其他类去继承,在这里我们提供一些属性的配置,以及一些基础的请求方法

typescript 复制代码
import axios from './axios'
import type { AxiosRequest, CustomResponse } from './types/index'
class Abstract {
  // 配置接口的baseUrl,这里用的是vite环境变量,可以根据需求自行修改
  protected baseURL: string = import.meta.env.VITE_BASEURL
  // 配置接口的请求头,这里仅简单配置一下
  protected headers: object = {
    'Content-Type': 'application/json;charset=UTF-8',
  }
  // 提供类的构造器,可以在这里修改一些基础参数如baseUrl
  constructor(baseURL?: string) {
    this.baseURL = baseURL ?? this.baseURL
  }
  // 重点!发起请求的方法
  // 这里的T是ts中泛型的用法,主要用于控制接口返回的类型,不熟悉ts的同学可以略过
  private apiAxios<T = any>({
    baseURL = this.baseURL,
    headers = this.headers,
    method,
    url,
    data,
    params,
    responseType
  }: AxiosRequest): Promise<CustomResponse<T>> {
  // 在这里加上请求头的好处在于,每次请求时都会动态读取存储的token值
  // 正如前面所说的,不要在创建axios实例时在header上配置token是因为,浏览器除非刷新,否则只会创建一次axios实例,它的header上的token的值不会发生变化,如果涉及到用户退出等清除token的操作,下次登录时获得的新token不会被使用
  Object.assign(headers, {
    // 根据情况使用localStorage或sessionStorage
    token: localStorage.getItem('token')                    
  })
  return new Promise((resolve, reject) => {
    axios({
      baseURL,
      headers,
      method,
      data,
      url,
      params,
      responseType,
    })
      .then(res => {
        // 在这里处理http2xx的接口,根据业务的需要进行一些处理,返回一个成功的promise
        // 这里仅为演示,直接返回了原始的res
        resolve(res)
      })
      .catch(err => {
        // 在这里处理http不成功的状态,并根据业务的需要进行一个处理,返回一个失败的promise
        reject(err)
      })
    })
  }
  // 通常我还会在基础类上封装一些现成的请求方法,如Get Post等,可以根据自己的需要封装其他的请求方法
  protected getReq<T = any>({ baseURL, headers, url, data, params, responseType, messageType }: AxiosRequest) {
    return this.apiAxios<T>({
      baseURL,
      headers,
      method: 'GET',
      url,
      data,
      params,
      responseType,
      messageType,
    })
  }
    
  protected postReq<T = any>({ baseURL, headers, url, data, params, responseType, messageType }: AxiosRequest) {
    return this.apiAxios<T>({
      baseURL,
      headers,
      method: 'POST',
      url,
      data,
      params,
      responseType,
      messageType,
    })
  }

3.继承Abstract类实现业务相关的请求类

这样,我们就成功封装了一个Axios的基础类,接下来可以创建一个新的业务类去继承它并使用。这里我们创建一个User类,代表用户相关的请求

typescript 复制代码
import Abstract from '@/api/abstract'
​
class User extends Abstract {
  constructor(baseUrl?: string) {
    super(baseUrl)
  }
​
  // post请求
  login(data: unknown) {
    return this.postReq({
      data,
      url: 'back/v1/user/login',
    })
  }
  
  // get请求
  getUser(param: { id: string }) 
    return this.getReq({
      param,
      url: 'back/v1/user/getUser'
    })
  }
  
  // 需要修改请求头的Content-Type,如表单上传
  saveUser(data: any) {
    const formData = new FormData()
    Object.keys(data).forEach(key => {
      formData.append('file', data[key])
    })
    return this.postReq({
      data,
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      url: 'back/v1/user/saveUser',
    })
  }
​
export default User

文件创建好了之后我们就可以引用到具体项目中使用了

php 复制代码
// 可以在这里传入baseUrl,这也是基于类封装的好处,我们可以实例化多个user并使用不同的baseUrl
const userInstance = new User()
const res = await userInstance.login({
  username: 'xxx',
  password: 'xxx'
})
相关推荐
mldong1 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排2 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰4 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen6 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马7 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu7 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss