通过TypeScript封装axios

2,521

划分代码文件结构

  • config: 导出一些全局变量
  • type: 定义一个类型接口
  • index: axios的封装文件

config文件

    // 配置请求超时时间,
    // 根据不同环境来改变baseUrl
    
    let BASE_URL = ''
    const TIME_OUT = 5000

    if (process.env.NODE_ENV === 'development') {
      BASE_URL = 'development'
    } else if (process.env.NODE_ENV === 'production') {
      BASE_URL = 'production'
    } else {
      BASE_URL = 'test'
    }

    export { BASE_URL, TIME_OUT }

type文件

如果我们不封装axios,我们直接使用axios提供的类型即可。但是我们需要提供一个拦截器函数,并且还需要展示loading,所以我们需要扩展一些内置的接口。

    import type { AxiosRequestConfig, AxiosResponse } from 'axios'
    
    // 定义传入的拦截器接口,并且都是可以可选的。
    interface IRequestInterceptors<T = AxiosResponse> {
      // 请求成功时的拦截器
      requestSuccessInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
      // 请求失败时的拦截器
      requestErrorInterceptor?: (err: any) => any
      // 响应成功时的拦截器
      responseSuccessInterceptor?: (res: T) => T
      // 响应失败时的拦截器
      responseErrorInterceptor?: (err: any) => any
    }

    // 这个接口将要代替AxiosRequestConfig
    export interface IRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
      // 每个request实例可以不传入拦截器
      interceptors?: IRequestInterceptors<T>
      // 是否显示loading
      showLoading?: boolean
    }

index文件

我们通过类来对axios封装,使其可以创建多个axios实例。有自己独特的状态。

定义axios的全局拦截器

他的目的是,每个axios实例和请求都需要做的操作,我们可以放在全局拦截器中。此时的拦截器我们在构造函数中定义其逻辑即可。

    import axios, { AxiosInstance } from 'axios'
    import { IRequestConfig } from './type'
    class Request {
      public instance: AxiosInstance

      constructor(config: IRequestConfig) {
        this.instance = axios.create(config)
        // 创建全局请求拦截器
        this.instance.interceptors.request.use(
          (config) => {
            console.log('全局请求成功创建的拦截器')
            return config
          },
          (err) => {
            console.log('全局请求失败创建的拦截器')
            return err
          }
        )
        // 创建全局响应拦截器
        this.instance.interceptors.response.use(
          (config) => {
            console.log('全局响应成功创建的拦截器')
            return config
          },
          (err) => {
            console.log('全局响应失败创建的拦截器')
            return err
          }
        )
      }
    }

定义axios的实例拦截器

这些拦截器其实和全局一样,如果没有创建多个axios实例的时候。大部分情况下,我们是不需要创建多个axios实例的。这时候我们扩展的AxiosRequestConfig接口就派上用场了。这些拦截器我们就需要在创建实例的时候传入对应的逻辑。然后再构造函数中注册即可.

    import axios, { AxiosInstance } from 'axios'
    import { IRequestConfig } from './type'
    class Request {
      public instance: AxiosInstance

      constructor(config: IRequestConfig) {
        this.instance = axios.create(config)
        // 创建实例请求拦截器
        this.instance.interceptors.request.use(
          config.interceptors?.requestSuccessInterceptor,
          config.interceptors?.requestErrorInterceptor
        )
        // 创建实例请求拦截器
        this.instance.interceptors.response.use(
          config.interceptors?.responseSuccessInterceptor,
          config.interceptors?.responseErrorInterceptor
        )
      }
    }

封装request函数

其实这个就是调用axios中的 request 函数做二次封装。

我们知道promise的优势,所以我们将request返回一个promise对象。但是promise需要传入一个泛型,作为返回值的类型。所以我们调用request函数的时候需要传入一个返回值的类型。并且我们可以在这里创建单个请求独有的拦截器。这些拦截器需要在调用request函数的时候传入对应的逻辑。并且调用传入的拦截器,并将其返回值给配置对象或者返回值。

 // 传入的泛型是约束返回值
  request<T>(config: IRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
        
      // 创建单个请求的请求拦截器
      if (config.interceptors?.requestSuccessInterceptor) {
        // 直接调用,然后将处理后的config返回
        config = config.interceptors.requestSuccessInterceptor(config)
      }
      this.instance
        .request<any, T>(config)
        .then((res) => {
          // 调用传入的响应拦截器
          if (config.interceptors?.responseSuccessInterceptor) {
            res = config.interceptors.responseSuccessInterceptor(res)
          }
          resolve(res)
        })
        .catch((err) => {
          reject(err)
        })
    })
  }

封装其他的请求方法。

这些方法都是借助上面封装的request方法。

      get<T>(config: IRequestConfig<T>): Promise<T> {
        return this.request<T>({ ...config, method: 'GET' })
      }

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

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

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

通过element-plus来加载loading

loading组件,我们可以通过element-plus中的el-loading来加载。并且以服务的方式进行调用。注意新版本的类型导入路径。

    
    import { ILoadingInstance } from 'element-plus/lib/components/loading'
    import axios, { AxiosInstance } from 'axios'
    import { IRequestConfig } from './type'
    import { ElLoading } from 'element-plus/lib/components'

    // 默认加载loading
    const DEFAULT_LOADING = true

    // 定义两个属性
    public showLoading: boolean
    public loadingInstance?: ILoadingInstance
    
    constructor(config: IRequestConfig) {
        // 默认不加载loading
        this.showLoading = config.showLoading ?? DEFAULT_LOADING
        this.instance = axios.create(config)
        // 创建全局请求拦截器
        this.instance.interceptors.request.use(
          (config) => {
            console.log('全局请求成功创建的拦截器')
            // 当showLoading为true是加载loading
            if (this.showLoading) {
              // 添加加载loading
              this.loadingInstance = ElLoading.service({
                text: '正在加载,请稍等...',
                background: 'rgba(0, 0, 0, .1)',
                lock: true
              })
            }
            return config
          },
          (err) => {
            console.log('全局请求失败创建的拦截器')
            // 请求错误,让loading关闭
            this.loadingInstance?.close()
            return err
          }
        )
         // 创建全局响应拦截器
        this.instance.interceptors.response.use(
          (config) => {
            console.log('全局响应成功创建的拦截器')
           // 响应时,让loading关闭
            this.loadingInstance?.close()
            return config
          },
          (err) => {
            console.log('全局响应失败创建的拦截器')
            // 响应出错时,让loading关闭
            this.loadingInstance?.close()
            return err
          }
        )
      }
      
  // 传入的泛型是约束返回值
  request<T>(config: IRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      // 定制该请求是否加loading。当为传入该参数时,默认为true
      if (config.showLoading === false) {
        this.showLoading = false
      }
      // 创建单个请求的请求拦截器
      if (config.interceptors?.requestSuccessInterceptor) {
        // 直接调用,然后将处理后的config返回
        config = config.interceptors.requestSuccessInterceptor(config)
      }
      this.instance
        .request<any, T>(config)
        .then((res) => {
          // 单个请求结束后,让其loading等于默认值。因为我们将showLoading挂载到Request实例上,状态一直被修改
          this.showLoading = DEFAULT_LOADING
          // 调用传入的响应拦截器
          if (config.interceptors?.responseSuccessInterceptor) {
            res = config.interceptors.responseSuccessInterceptor(res)
          }
          resolve(res)
        })
        .catch((err) => {
          // 单个请求结束后,让其loading等于默认值。因为我们将showLoading挂载到Request实例上,状态一直被修改
          this.showLoading = DEFAULT_LOADING
          reject(err)
        })
    })
  }

完整的index文件代码

    import { ILoadingInstance } from 'element-plus/lib/components/loading'
    import axios, { AxiosInstance } from 'axios'
    import { IRequestConfig } from './type'
    import { ElLoading } from 'element-plus/lib/components'

    const DEFAULT_LOADING = true

    class Request {
      public instance: AxiosInstance
      public showLoading: boolean
      public loadingInstance?: ILoadingInstance

      constructor(config: IRequestConfig) {
        // 默认不加载loading
        this.showLoading = config.showLoading ?? DEFAULT_LOADING
        this.instance = axios.create(config)
        // 先创建实例请求拦截器
        this.instance.interceptors.request.use(
          config.interceptors?.requestSuccessInterceptor,
          config.interceptors?.requestErrorInterceptor
        )
        // 先创建实例请求拦截器
        this.instance.interceptors.response.use(
          config.interceptors?.responseSuccessInterceptor,
          config.interceptors?.responseErrorInterceptor
        )
        // 创建全局请求拦截器
        this.instance.interceptors.request.use(
          (config) => {
            console.log('全局请求成功创建的拦截器')
            if (this.showLoading) {
              // 添加加载loading
              this.loadingInstance = ElLoading.service({
                text: '正在加载,请稍等...',
                background: 'rgba(0, 0, 0, .1)',
                lock: true
              })
            }
            return config
          },
          (err) => {
            console.log('全局请求失败创建的拦截器')
            this.loadingInstance?.close()
            return err
          }
        )
        // 创建全局响应拦截器
        this.instance.interceptors.response.use(
          (config) => {
            console.log('全局响应成功创建的拦截器')
            setTimeout(() => {
              this.loadingInstance?.close()
            }, 3000)
            return config
          },
          (err) => {
            console.log('全局响应失败创建的拦截器')
            this.loadingInstance?.close()
            return err
          }
        )
      }

      // 传入的泛型是约束返回值
      request<T>(config: IRequestConfig<T>): Promise<T> {
        return new Promise((resolve, reject) => {
          // 定制该请求是否加loading。当为传入该参数时,默认为true
          if (config.showLoading === false) {
            this.showLoading = false
          }
          // 创建单个请求的请求拦截器
          if (config.interceptors?.requestSuccessInterceptor) {
            // 直接调用,然后将处理后的config返回
            config = config.interceptors.requestSuccessInterceptor(config)
          }
          this.instance
            .request<any, T>(config)
            .then((res) => {
              this.showLoading = DEFAULT_LOADING
              // 调用传入的响应拦截器
              if (config.interceptors?.responseSuccessInterceptor) {
                res = config.interceptors.responseSuccessInterceptor(res)
              }
              resolve(res)
            })
            .catch((err) => {
              this.showLoading = DEFAULT_LOADING
              reject(err)
            })
        })
      }

      get<T>(config: IRequestConfig<T>): Promise<T> {
        return this.request<T>({ ...config, method: 'GET' })
      }

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

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

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

    export default Request

测试

    import Request from './http/request'
    import { BASE_URL, TIME_OUT } from './http/request/config'
    const request = new Request({
      baseURL: BASE_URL,
      timeout: TIME_OUT,
      showLoading: true,
      interceptors: {
        requestSuccessInterceptor(config) {
          console.log('Request实例请求成功的拦截器')
          return config
        },
        requestErrorInterceptor(err) {
          console.log('Request实例请求失败的拦截器')
          return err
        },
        responseSuccessInterceptor(res) {
          console.log('Request实例响应成功的拦截器')
          return res
        },
        responseErrorInterceptor(err) {
          console.log('Request实例响应失败的拦截器')
          return err
        }
      }
    })

    interface IRequestData {
      data: any
    }

    request
      .get<IRequestData>({
        url: 'search?keywords=海阔天空',
        showLoading: true,
        interceptors: {
          requestSuccessInterceptor(config) {
            console.log('get请求的拦截器')
            return config
          }
        }
      })
      .then((res) => {
        console.log('res ====', res)
      })

image.png