使用TS对axios进行封装

124 阅读5分钟

在实际开发中,在多个组件中都需要进行网络请求,那么就需要在多个组件中都需要使用axios来进行网络请求

这就会导致项目和axios的耦合度,也就是关联度过高,如果哪天axios发生了重大更新或者不维护的时候,需要修改和变动的地方就非常多

所以在实际开发中,会单独对网络请求进行二次封装,也就是在axios和业务逻辑之间添加上单独的一层

这样对于项目业务逻辑而言使用的是自己封装的内容,实际在项目中对于axios的引用只有一处

这样可以减低项目和axios之间的耦合度,便于后续的升级和维护

简单封装

/service/index.ts

import { BASE_URL, TIMEOUT } from './config';
import API from './api'

export const api = new API({
  baseURL: BASE_URL,
  timeout: TIMEOUT
})

/service/config.ts

将axios的配置选项进行抽离,方便在多处使用相同的配置选项,也方便后期对这些配置选项进行修改和维护

// 常量 全部字符大写表示 单词和单词之间使用下划线进行连接
export const BASE_URL = 'http://www.example.com:8000'
export const TIMEOUT = 10000

/service/api.ts

import axios from 'axios'
import type { AxiosRequestConfig, AxiosInstance } from 'axios'

// 对于关联度比较高的属性和方法一般会单独封装成一个类
// 一般对应的网络请求方法会被封装到src/api文件夹中或src/services文件夹下
class API {
  instance: AxiosInstance

  constructor(config: AxiosRequestConfig) {
    // 每次创建实例的时候,都调用axios.create方法
    // axios.create可以返回一个axios实例
    // 这样保证我们可以使用不同的配置创建多个axios实例
    this.instance = axios.create(config)
  }

  // request是最为核心的方法
	// get, post等方法 本质上调用的还是request方法
  request(config: AxiosRequestConfig) {
    return this.instance.request(config)
  }
}

export default API

测试

import { api } from './service'

async function fetchMultidata() {
  const res = await api.request({
    url: '/home/multidata'
  })

  console.log(res.data)
}

fetchMultidata()

拦截器

全局拦截器

全局拦截器是那些所有实例对象都需要使用的拦截器

import axios from 'axios'
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
import type { RequestConfig } from './type'

class API {
  instance: AxiosInstance

  constructor(config: RequestConfig) {
    this.instance = axios.create(config)

    // 这里编写所有组件实例都需要进行的请求和响应拦截
    // 在请求拦截中 可以修改配置/加载loading动画/添加token等
    // 在响应拦截中,可以对返回的数据进行二次处理后再返回 或者关闭loading动画
  	this.instance.interceptors.request.use(config => config, err => err)
    this.instance.interceptors.response.use(res => res.data, err => err)
  }

  request(config: AxiosRequestConfig) {
    return this.instance.request(config)
  }
}

export default API

实例拦截器

实例拦截器是针对于某一个具体的axios实例所需要使用的拦截器

type.ts

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

// 继承自axios的AxiosRequestConfig 是对AxiosRequestConfig的扩展
export interface RequestConfig extends AxiosRequestConfig {
  interceptors?: {
    requestResolved?: (config: AxiosRequestConfig) => AxiosRequestConfig;
    requestRejected?: (err: any) => any;
    responseResolved?: (res: AxiosResponse) => AxiosResponse;
    responseRejected?: (err: any) => any;
  }
}

api.ts

import axios from 'axios'
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
import type { RequestConfig } from './type'

class API {
  instance: AxiosInstance

  constructor(config: RequestConfig) {
    this.instance = axios.create(config)

    this.instance.interceptors.request.use(config => config, err => err)
    this.instance.interceptors.response.use(res => res.data, err => err)

    // 拦截器 对应use方法的 成功回调函数和失败回调函数 都是可选的
    // 拦截器可以添加多个,多个拦截器对应的函数会组合成数组并依次进行调用
    this.instance.interceptors.request.use(config.interceptors?.requestResolved, config.interceptors?.requestRejected)
    this.instance.interceptors.response.use(config.interceptors?.responseResolved, config.interceptors?.responseRejected)
  }

  request(config: AxiosRequestConfig) {
    return this.instance.request(config)
  }
}

export default API

测试

import { BASEURL, TIMEOUT } from './config';
import API from './api'

export const api = new API({
  baseURL: BASE_URL,
  timeout: TIMEOUT,
  interceptors: {
    requestResolved(config) {
      // 在这里可以对某一个实例的请求进行单独的拦截处理
      console.log('全局成功的请求拦截')
      return config
    },

    responseResolved(res) {
      // 在这里可以对某一个实例的响应进行单独的拦截处理
      console.log('全局成功的响应拦截')
      return res.data
    }
  }
})

接口拦截器

接口拦截器是针对于某一个具体的请求所需要使用到的拦截器

import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { RequestConfig } from './type'

class API {
  instance: AxiosInstance

  constructor(config: RequestConfig) {
    this.instance = axios.create(config)

    this.instance.interceptors.request.use(config => config, err => err)
    this.instance.interceptors.response.use(res => res.data, err => err)

    this.instance.interceptors.request.use(config.interceptors?.requestResolved, config.interceptors?.requestRejected)
    this.instance.interceptors.response.use(config.interceptors?.responseResolved, config.interceptors?.responseRejected)
  }

  request(config: RequestConfig) {
    // 接口拦截器不能直接使用instance.interceptors
    // 因为instance.interceptors是挂载到axios实例对象上的
    // 这意味着后续所有的操作都会存在接口级别的网络请求
    // 所以添加接口拦截器最好的方法就是手动执行传入的回调

    try {
      if (config.interceptors?.requestResolved) {
        config = config.interceptors.requestResolved(config)
      }

      return new Promise((resolve, reject) => {
          this.instance.request(config).then(res => {
            if (config.interceptors?.responseResolved) {
              res = config.interceptors.responseResolved(res)
            }
            resolve(res)
          }).catch(err => {
            if (config.interceptors?.responseRejected) {
              err = config.interceptors.responseRejected(err)
            }
            reject(err)
          })
      })
    } catch(err) {
      if (config.interceptors?.requestRejected) {
        config = config.interceptors.requestRejected(err)
      }
    }
  }
}

export default API

测试

import { api } from './service'

async function fetchMultidata() {
  const res = await api.request({
    url: '/home/multidata',
    interceptors: {
      requestResolved(config) {
        console.log('接口层面的请求成功拦截')
        return config
      },
      responseResolved(res) {
        console.log('接口层面的响应成功拦截')
        return res
      }
    }
  })

  console.log(res)
}

fetchMultidata()

类型问题

但是此时会发现返回的结果都是unlnown类型,因为默认情况下axios无法知道我们请求的实际返回值类型

所以我们希望通过添加泛型的方式,手动告诉axios对应的返回结果的类型

type.ts

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: {
    requestResolved?: (config: AxiosRequestConfig) => AxiosRequestConfig;
    requestRejected?: (err: any) => any;
    responseResolved?: (res: T) => T;
    responseRejected?: (err: any) => any;
  }
}

api.ts

import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { RequestConfig } from './type'

class API {
  instance: AxiosInstance

  constructor(config: RequestConfig) {
    this.instance = axios.create(config)

    this.instance.interceptors.request.use(config => config, err => err)
    this.instance.interceptors.response.use(res => res.data, err => err)

    this.instance.interceptors.request.use(config.interceptors?.requestResolved, config.interceptors?.requestRejected)
    this.instance.interceptors.response.use(config.interceptors?.responseResolved, config.interceptors?.responseRejected)
  }

  request<T>(config: RequestConfig<T>) {
    try {
      if (config.interceptors?.requestResolved) {
        config = config.interceptors.requestResolved(config)
      }

      // Promise如果不传泛型 那么默认的返回值的类型就是unknown
      return new Promise<T>((resolve, reject) => {
          this.instance.request<any, T>(config).then(res => {
            if (config.interceptors?.responseResolved) {
              res = config.interceptors.responseResolved(res)
            }
            resolve(res)
          }).catch(err => {
            if (config.interceptors?.responseRejected) {
              err = config.interceptors.responseRejected(err)
            }
            reject(err)
          })
      })
    } catch(err) {
      if (config.interceptors?.requestRejected) {
        config = config.interceptors.requestRejected(err)
      }
    }
  }
}

export default API

其余方法的简单封装

type.ts

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface IData {
  [key: string]: unknown
}

export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: {
    requestResolved?: (config: AxiosRequestConfig) => AxiosRequestConfig;
    requestRejected?: (err: any) => any;
    responseResolved?: (res: T) => T;
    responseRejected?: (err: any) => any;
  }
}
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { RequestConfig, IData } from './type'

class API {
  instance: AxiosInstance

  constructor(config: RequestConfig) {
    this.instance = axios.create(config)

    this.instance.interceptors.request.use(config => config, err => err)
    this.instance.interceptors.response.use(res => res.data, err => err)

    this.instance.interceptors.request.use(config.interceptors?.requestResolved, config.interceptors?.requestRejected)
    this.instance.interceptors.response.use(config.interceptors?.responseResolved, config.interceptors?.responseRejected)
  }

  request<T>(config: RequestConfig<T>) {
    try {
      if (config.interceptors?.requestResolved) {
        config = config.interceptors.requestResolved(config)
      }

      return new Promise<T>((resolve, reject) => {
          this.instance.request<any, T>(config).then(res => {
            if (config.interceptors?.responseResolved) {
              res = config.interceptors.responseResolved(res)
            }
            resolve(res)
          }).catch(err => {
            if (config.interceptors?.responseRejected) {
              err = config.interceptors.responseRejected(err)
            }
            reject(err)
          })
      })
    } catch(err) {
      if (config.interceptors?.requestRejected) {
        config = config.interceptors.requestRejected(err)
      }
    }
  }

  // 对于get,post等请求,config参数可以不传递
  get<T>(url: string, config?: RequestConfig<T>) {
    return this.request<T>({url, ...config, method: 'GET'})
  }

  post<T>(url: string, data: IData, config?: RequestConfig<T>) {
    return this.request<T>({url, data, ...config, method: 'POST'})
  }

  delete<T>(url: string, config?: RequestConfig<T>) {
    return this.request<T>({url, ...config, method: 'DELETE'})
  }

  put<T>(url: string, data: IData, config?: RequestConfig<T>) {
    return this.request<T>({url, data, ...config, method: 'PUT'})
  }

  patch<T>(url: string, data: IData, config?: RequestConfig<T>) {
    return this.request<T>({url, data, ...config, method: 'PATCH'})
  }
}

export default API