使用TypeScript 封装axios

3,083 阅读7分钟

使用TypeScript 封装axios

优点

如果我们在项目中直接使用axios,项目中每个模块对axios的依赖性都很强。假如有一天axios因为某些原因不再维护而要用到另一个库的时候,我们就要全局一个一个的把axios给替换掉。这样对项目后续的维护升级不方便

image-20220414141744270.png

我们把axios封装在一个文件,让axios跟其他模块的耦合度降低,以后要更换成别的库的话可以直接在这个文件里面更换即可。

为什么使用TypeScript

可以对代码进行类型校验,让我们的代码更加安全可靠

封装要点

  • 可以创建不同axios实例,这样每个实例都可以定义自己baseURL等属性
  • 每个axios实例除了可以传入基本属性外,还可以传入自定义的拦截器
  • 每个单独的请求可以自定义拦截器

开始

安装

npm install axios -D

封装请求类

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

class LFRequest {
   // 有时候我们不同的请求需要不同的baseUrl,如果在同一个axios上定义会被覆盖掉, 我们可以用axios.create(config)创建不同的实例
  instance: AxiosInstance
  constructor(config: AxiosRequestConfig) {
    // 一开始我们不知道要用什么类型 鼠标放在create上   ctrl+点击可以到达源码
    // 创建axios实例
    // 有时候我们不同的请求需要不同的baseUrl,如果在同一个axios实例上定义会被覆盖掉, 我们可以创建不同的实例
    this.instance = axios.create(config)
  }
  request(config: AxiosRequestConfig): void {
    this.instance.request(config).then((res) => {
      console.log(res)
    })
  }
}

// 使用
const lfRequest = new LFRequest({
  baseURL: '自己定义',  
  timeout: '1000'
})

封装拦截器

拦截器应用

  • 每次请求在请求头带上token或者cookie
  • 请求没有返回的结果时候显示loading动画

可以根据需要是否带上cookie以及显示loading动画

拦截器使用

axios.interceptors.request.use(fn1, fn2)

axios.interceptors.response.use(fn1, fn2)

// 添加请求拦截器
axios.interceptors.request.use(function (config) {    
    // 在发送请求之前做些什么    
    return config;  
}, function (error) {    
    // 对请求错误做些什么    
    return Promise.reject(error);  
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) { 
    // 对响应数据做点什么    
    return response;  
}, function (error) {    
    // 对响应错误做点什么   
    return Promise.reject(error);  
});

我们在创建新的axios实例的时候除了传入基本属性外还可以传入自定义的拦截器, 例如

const lfRequest = new LFRequest({
  baseURL: '自己定义',  
  timeout: '1000',
  interceptors: {}  // 拦截器对象
})

某个axios实例独有的拦截器

定义拦截器函数接口
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
//  泛型类
interface LFRequestInterceptors {
  // 函数
  // 源码
  // interceptors: {
  //  request: AxiosInterceptorManager<AxiosRequestConfig>;  // 类型 AxiosRequestConfig
  //  response: AxiosInterceptorManager<AxiosResponse>;
  // };
  // 泛型类 V:AxiosRequestConfig  T = V
  // export interface AxiosInterceptorManager<V> {
  //  use<T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number;
  //  eject(id: number): void;
  // }
  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorsCatch?: (error: any) => any
  responseInterceptor: (res: AxiosResponse) => AxiosResponse
  responseInterceptorCatch: (error: any) => any
}
拓展 AxiosRequestConfig

我们希望创建axios实例除了传入基础参数外,还可以传入拦截器函数

interface LFRequestConfig extends AxiosRequestConfig {
  interceptors?: LFRequestInterceptors
}
传入拦截器函数时进行拦截
const lfRequest = new LFRequest({
  baseURL: '自己定义',
  timeout: 1000,
  interceptors: {
    requestInterceptor(config) {
      console.log('成功拦截')
      return config
    },
    requestInterceptorCatch: (err) => {
      console.log('请求失败的拦截')
      return err
    },
    responseInterceptor: (res) => {
      console.log('响应成功的拦截')
      return res
    },
    responseInterceptorCatch: (err) => {
      console.log('响应失败的拦截')
      return err
    }
  }
})


class LFRequest {
  instance: AxiosInstance
  // 存放实例传入拦截器函数
  interceptors?: LFRequestInterceptors
  constructor(config: LFRequestConfig) {
    // 创建axios实例
    this.instance = axios.create(config)
    // 保存实例传入拦截器函数
	this.interceptors = config.interceptors
    // 请求拦截器/响应拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptors,
      this.interceptors?.requestInterceptorsCatch
    )
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )
  }
  request(config: AxiosRequestConfig): void {
    this.instance.request(config).then((res) => {
      console.log(res)
    })
  }
}

所有axios实例都有的拦截器

class LFRequest {
  constructor(config: LFRequestConfig) {
    // ... 省略一些代码
    // 每个实例都有的请求拦截器 每次创建实例的时候这些拦截器也会被创建
    this.instance.interceptors.request.use((config) => {
      console.log('所有实例都有的拦截器: 请求成功拦截')
      return config
    }, (err) => {
      console.log('所有实例都有的拦截器: 请求失败拦截')
      return err
    })
    // 每个实例都有的响应拦截器
    this.instance.interceptors.response.use((res) => {
      console.log('所有实例都有的拦截器: 响应成功拦截')
      return res
    }, (err) => {
      console.log('所有实例都有的拦截器: 响应失败拦截')
      return err
    })
  }
}

某次请求自定义拦截器

lfRequest.request({
  url: '路径',
  method: 'GET',
  interceptors: {
    requestInterceptor: (config) => {
      console.log('单独请求的config')
      return config
    },
    requestInterceptorCatch: (err) => {
      console.log(err, '请求失败的拦截')
      return err
    }
  }
})
class LFRequest {
  // ...代码省略
  // AxiosRequestConfig 替换成扩展的接口 LFRequestConfig
  request(config: LFRequestConfig): void {
    // 1.单个请求对请求config的处理
    if (config.interceptors?.requestInterceptor) {
      // 调用requestInterceptor会返回config
      config = config.interceptors.requestInterceptor(config)
    }
    this.instance.request(config).then((res) => {
      console.log(res)
    })
  }
}

使用拦截器在请求头上加token

const lfRequest = new LFRequest({
  baseURL: 'lalala',
  timeout: 10000,
  // 这样每个实例都有不同的拦截器
  interceptors: {
    requestInterceptor(config) {
      // 一般token放在vuex或者缓存中
      const token = ''
      // config.headers 类型检查不通过 有两种方式
      // 第一种
      // if (!config.headers) {
      //   config.headers = {}
      // }
      // 第二种
      config.headers = config.headers ?? {}
      if (token) {
        // Bearer 信差
        config.headers.Authorization = `Bearer ${token}`
      }
      return config
    }
  }
})

请求未响应时显示loading

假如我们要给axios实例每个请求都加上loading效果

// element-plus 版本 2.1.9
import { ElLoading } from 'element-plus'
// 按需引入,如果不引入css 样式不生效
import 'element-plus/theme-chalk/base.css'   // 不同大版本放置的目录不同
import 'element-plus/theme-chalk/el-loading.css'

class LFRequest {
  // ...代码省略
  // 加载实例
  loading: any
  this.instance.interceptors.request.use((config) => {
    console.log('所有实例都有的拦截器: 请求成功拦截')
    // 初始化loading
    this.loading = ElLoading.service({
      lock: true,
      text: 'Loading',
      background: 'rgba(0, 0, 0, 0.5)'
    })
    return config
  },(err) => {
    console.log('所有实例都有的拦截器: 请求失败拦截')
    return err
  })
  // 每个实例都有的响应拦截器
  this.instance.interceptors.response.use((res) => {
    // 将loading移除
    this.loading?.close()
    console.log('所有实例都有的拦截器: 响应成功拦截')
    return res
  },(err) => {
    // 将loading移除
    this.loading?.close()
    console.log('所有实例都有的拦截器: 响应失败拦截')
    return err
  })
}

某次请求决定要不要显示loading

// 扩展request的配置参数
interface LFRequestConfig extends AxiosRequestConfig {
  interceptors?: LFRequestInterceptors
  showLoading?: boolean
}
class LFRequest {
  loading: any
  showLoading?: boolean
  constructor(config: LFRequestConfig) {
    // 每个实例都有的请求拦截器
    this.instance.interceptors.request.use(
      (config) => {
        console.log('所有实例都有的拦截器: 请求成功拦截')
        if (this.showLoading) {
          this.loading = ElLoading.service({
            lock: true,
            text: 'Loading',
            background: 'rgba(0, 0, 0, 0.5)'
          })
        }
        return config
      },
      (err) => {
        console.log('所有实例都有的拦截器: 请求失败拦截')
        return err
      }
    )
    // 每个实例都有的响应拦截器
    this.instance.interceptors.response.use(
      (res) => {
        // 将loading移除
        this.loading?.close()
        console.log('所有实例都有的拦截器: 响应成功拦截')
        return res
      },
      (err) => {
        // 将loading移除
        this.loading?.close()
        console.log('所有实例都有的拦截器: 响应失败拦截')
        return err
      }
    )
  }
  request(config: LFRequestConfig): void {
    // 判断这次请求是否要显示loading
    this.showLoading = config.showLoading ?? true
  }
}

lfRequest.request({
  url: 'lalala',
  method: 'GET',
  showLoading: false
})

优化请求响应参数

接口返回的数据是这样的

image-20220416113720528.png

但是通过request请求拿到的数据,axios会进行一层封装,只有data里面的数据是我们需要的

image-20220416113836711.png


// 返回一个promise 
request(config: LFRequestConfig): Promise<AxiosResponse> {
  return new Promise((resolve, reject) => {
    this.showLoading = config.showLoading ?? true
    // 1.单个请求对请求config的处理
    // 请求拦截器
    if (config.interceptors?.requestInterceptor) {
      // 调用requestInterceptor会返回config
      config = config.interceptors.requestInterceptor(config)
    }
    this.instance
      .request(config)
      .then((res) => {
        // 响应拦截器
        if (config.interceptors?.responseInterceptor) {
          res = config.interceptors.responseInterceptor(res)
        }
         // 将showLoading设置false, 这样不会影响下一个请求
        this.showLoading = false
        resolve(res.data)
      })
      .catch((err) => {
        this.showLoading = false
        reject(err)
      })
  })
}

image-20220421215348126.png

有时候我们知道后端返回的数据结构以及格式,我们可以定义返回值的类型,那样我们使用返回值的时候可以有很好的提示,也不容易出现一些低级错误。

image-20220421230044681.png

interface DataType {
  data: any
  returnCode: string
  success: boolean
}
// 泛型
lfRequest.request<Datatype>({
  // 代码省略
})

// 修改request方法
// 把 T 一层一层传递  : Promise<T>  把T传递给返回值
// LFRequestConfig<T> 把T传给拦截器
request<T>(config: LFRequestConfig<T>): Promise<T> {
  return new Promise((resolve, reject) => {
    this.showLoading = config.showLoading ?? true
    // 1.单个请求对请求config的处理
    if (config.interceptors?.requestInterceptor) {
      // 调用requestInterceptor会返回config
      config = config.interceptors.requestInterceptor(config)
    }
    // 通过request 把 T 传递进去
    this.instance.request<any, T>(config).then((res) => {
      if (config.interceptors?.responseInterceptor) {
        res = config.interceptors.responseInterceptor(res)
      }
      // 将showLoading设置false, 这样不会影响下一个请求
      this.showLoading = false
      resolve(res)
    }).catch((err) => {
      // 将showLoading设置false, 这样不会影响下一个请求
      this.showLoading = false
      reject(err)
    })
  })
}
// 通过 LFRequestConfig 接口 把 T传给拦截器
interface LFRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: LFRequestInterceptors<T>
  showLoading?: boolean
}
interface LFRequestInterceptors<T = AxiosResponse> {
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (res: T) => T
  responseInterceptorCatch?: (error: any) => any
}

image-20220421231055658.png

封装GETPOST等请求方法

class LFRequest {
  // 代码省略
  get<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }

  post<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }

  delete<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }

  patch<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PATCH' })
  }
}

加油,每天进步一点点!!!