axios+ts(详细拆分封装)

记录一下项目中的axios封装

将代码分成 5 个文件

axios+ts(详细拆分封装)_第1张图片

1.axios.ts 代码的核心封装

import axios, {
  AxiosInstance,
  InternalAxiosRequestConfig,
  AxiosResponse,
} from 'axios'
import type { Result, UploadFileParams, RequestOptions } from './type'
import { cloneDeep } from 'lodash-es'
import { isFunction } from '@/utils/is'
import { AxiosCanceler } from './axiosCancel'

export class VAxios {
  private service: AxiosInstance
  private options: any
  constructor(config: InternalAxiosRequestConfig) {
    this.options = config
    this.service = axios.create(config)
    this.setupInterceptors()
  }

  /**
   * @description 获取请求拦截器实例
   * @returns
   */
  private getTransform() {
    const { transform } = this.options
    return transform
  }

  /**
   * @description: 重新配置axios
   */
  configAxios(config: any) {
    if (!this.service) {
      return
    }
    this.createAxios(config)
  }

  /**
   * @description:  创建axios实例
   */
  private createAxios(config: any): void {
    this.service = axios.create(config)
  }

  /**
   * @description 获取axios实例
   * @returns
   */
  getAxios(): AxiosInstance {
    return this.service
  }

  /**
   * @description: 设置通用header
   */
  setHeader(headers: any): void {
    if (!this.service) {
      return
    }
    Object.assign(this.service.defaults.headers, headers)
  }

  /**
   * @description:   请求方法
   */
  request(
    config: InternalAxiosRequestConfig,
    options?: RequestOptions
  ): Promise {
    let conf: InternalAxiosRequestConfig = cloneDeep(config)
    const transform = this.getTransform()

    const { requestOptions } = this.options
    const opt: RequestOptions = Object.assign({}, requestOptions, options)

    const { beforeRequestHook, requestCatch, transformRequestData } =
      transform || {}
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = beforeRequestHook(conf, opt)
    }
    if (import.meta.env.VITE_WEB_BASE_API) {
      conf.url = import.meta.env.VITE_WEB_BASE_API + conf.url
    } else {
      conf.url = import.meta.env.VITE_SERVER + conf.url
    }

    //这里重新 赋值成最新的配置
    // @ts-ignore
    conf.requestOptions = opt

    return new Promise((resolve, reject) => {
      this.service
        .request>(conf)
        .then((res: AxiosResponse) => {
          // 请求是否被取消
          const isCancel = axios.isCancel(res)
          if (
            transformRequestData &&
            isFunction(transformRequestData) &&
            !isCancel
          ) {
            try {
              const ret = transformRequestData(res, opt)
              resolve(ret)
            } catch (err) {
              reject(err || new Error('request error!'))
            }
            return
          }
          resolve(res as unknown as Promise)
        })
        .catch((e: Error) => {
          if (requestCatch && isFunction(requestCatch)) {
            reject(requestCatch(e))
            return
          }
          reject(e)
        })
    })
  }

  /**
   * @description:  文件上传
   */
  uploadFile(
    config: InternalAxiosRequestConfig,
    params: UploadFileParams
  ) {
    const formData = new window.FormData()
    const customFilename = params.name || 'file'
    if (params.filename) {
      formData.append(customFilename, params.file, params.filename)
    } else {
      formData.append(customFilename, params.file)
    }
    if (params.data) {
      Object.keys(params.data).forEach((key) => {
        const value = params.data![key]
        if (Array.isArray(value)) {
          value.forEach((item) => {
            formData.append(`${key}[]`, item)
          })
          return
        }

        formData.append(key, params.data![key])
      })
    }

    return this.service.request({
      method: 'POST',
      data: formData,
      ...config,
    })
  }
  /**
   * @description: 拦截器配置
   */
  setupInterceptors() {
    const transform = this.getTransform()
    if (!transform) {
      return
    }
    const {
      requestInterceptors,
      requestInterceptorsCatch,
      responseInterceptors,
      responseInterceptorsCatch,
    } = transform

    const axiosCanceler = new AxiosCanceler()
    this.service.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // 请求之前拦截
        // window.$loading.start()
        const {
          headers: { ignoreCancelToken },
        } = config as any
        const ignoreCancel =
          ignoreCancelToken !== undefined
            ? ignoreCancelToken
            : this.options.requestOptions?.ignoreCancelToken

        !ignoreCancel && axiosCanceler.addPending(config)
        if (requestInterceptors && isFunction(requestInterceptors)) {
          config = requestInterceptors(config, this.options)
        }
        return config
      }
    )

    // 请求拦截器错误捕获
    requestInterceptorsCatch &&
      isFunction(requestInterceptorsCatch) &&
      this.service.interceptors.request.use(undefined, requestInterceptorsCatch)
    this.service.interceptors.response.use((res: AxiosResponse) => {
      // 请求后拦截
      res && axiosCanceler.removePending(res.config)
      if (responseInterceptors && isFunction(responseInterceptors)) {
        res = responseInterceptors(res)
      }
      // window.$loading.finish()
      return res
    })
    // 响应结果拦截器错误捕获
    responseInterceptorsCatch &&
      isFunction(responseInterceptorsCatch) &&
      this.service.interceptors.response.use(
        undefined,
        responseInterceptorsCatch
      )
  }
}

window.$loading全局注入的loading,使用时用自己项目的loading,不用就注释调

isFunction判断是否是函数

/**
 * @description 判断数据类型
 * @param value
 * @param type
 * @returns
 */
export function isTypeof(value: any, type: string): boolean {
  return typeof value === type
}

/**
 * @description 判断是否是函数
 * @param value
 * @returns
 */
export function isFunction(value: any): boolean {
  return isTypeof(value, 'function')
}

lodash-es是一个非常优秀的工具库

2.type.ts 类型定义

import { type AxiosRequestConfig } from 'axios'
import { AxiosTransform } from './axiosTransform'

export interface CreateAxiosOptions extends AxiosRequestConfig {
  transform?: AxiosTransform
  requestOptions?: RequestOptions
  authenticationScheme?: string
}

// 上传文件
export interface UploadFileParams {
  // 其他参数
  data?: any
  // 文件参数接口字段名
  name?: string
  // 文件
  file: File | Blob
  // 文件名称
  filename?: string
  [key: string]: any
}

export interface RequestOptions {
  // 请求参数拼接到url
  joinParamsToUrl?: boolean
  // 格式化请求参数时间
  formatDate?: boolean
  // 是否显示提示信息
  isShowMessage?: boolean
  // 是否解析成JSON
  isParseToJson?: boolean
  // 成功的文本信息
  successMessageText?: string
  // 是否显示成功信息
  isShowSuccessMessage?: boolean
  // 是否显示失败信息
  isShowErrorMessage?: boolean
  // 错误的文本信息
  errorMessageText?: string
  // 是否加入url
  joinPrefix?: boolean
  // 接口地址, 不填则使用默认apiUrl
  apiUrl?: string
  // 请求拼接路径
  urlPrefix?: string
  // 错误消息提示类型
  errorMessageMode?: 'none' | 'modal'
  // 是否添加时间戳
  joinTime?: boolean
  // 不进行任何处理,直接返回
  isTransformResponse?: boolean
  // 是否返回原生响应头
  isReturnNativeResponse?: boolean
  //忽略重复请求
  ignoreCancelToken?: boolean
  // 是否携带token
  withToken?: boolean
}

export interface Result {
  code: number
  msg: string
  data?: T
}

3.axiosTransform.ts 是用于封装和扩展 Axios 请求的核心配置

import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import type { RequestOptions, Result } from './type'

export interface CreateAxiosOptions extends AxiosRequestConfig {
  authenticationScheme?: string
  transform?: AxiosTransform
  requestOptions?: RequestOptions
}

export abstract class AxiosTransform {
  /**
   * @description: 请求之前处理配置
   * @description: Process configuration before request
   */
  beforeRequestHook?: (
    config: AxiosRequestConfig,
    options: RequestOptions
  ) => AxiosRequestConfig

  /**
   * @description: 请求成功处理
   */
  transformRequestData?: (
    res: AxiosResponse,
    options: RequestOptions
  ) => any

  /**
   * @description: 请求失败处理
   */
  requestCatch?: (e: Error) => Promise

  /**
   * @description: 请求之前的拦截器
   */
  requestInterceptors?: (
    config: AxiosRequestConfig,
    options: CreateAxiosOptions
  ) => AxiosRequestConfig

  /**
   * @description: 请求之后的拦截器
   */
  responseInterceptors?: (res: AxiosResponse) => AxiosResponse

  /**
   * @description: 请求之前的拦截器错误处理
   */
  requestInterceptorsCatch?: (error: Error) => void

  /**
   * @description: 请求之后的拦截器错误处理
   */
  responseInterceptorsCatch?: (error: Error) => void
}

4.axiosCancel 取消请求

import axios, { type AxiosRequestConfig, type Canceler } from 'axios'
import qs from 'qs'

import { isFunction } from '@/utils/is/index'

// 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map()

export const getPendingUrl = (config: AxiosRequestConfig) =>
  [
    config.method,
    config.url,
    qs.stringify(config.data),
    qs.stringify(config.params),
  ].join('&')

export class AxiosCanceler {
  /**
   * 添加请求
   * @param {Object} config
   */
  addPending(config: AxiosRequestConfig) {
    this.removePending(config)
    const url = getPendingUrl(config)
    config.cancelToken =
      config.cancelToken ||
      new axios.CancelToken((cancel) => {
        if (!pendingMap.has(url)) {
          // 如果 pending 中不存在当前请求,则添加进去
          pendingMap.set(url, cancel)
        }
      })
  }

  /**
   * @description: 清空所有pending
   */
  removeAllPending() {
    pendingMap.forEach((cancel) => {
      cancel && isFunction(cancel) && cancel()
    })
    pendingMap.clear()
  }

  /**
   * 移除请求
   * @param {Object} config
   */
  removePending(config: AxiosRequestConfig) {
    const url = getPendingUrl(config)

    if (pendingMap.has(url)) {
      // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
      const cancel = pendingMap.get(url)
      cancel && cancel(url)
      pendingMap.delete(url)
    }
  }

  /**
   * @description: 重置
   */
  reset(): void {
    pendingMap = new Map()
  }
}

5.index.ts 暴露接口可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动

import { VAxios } from './axios'
import { AxiosTransform } from './axiosTransform'
import { type AxiosResponse } from 'axios'
import { ContentTypeEnum, ResultEnum } from '@/enums/httpEnum'
import { useUserStore } from '@/stores/modules'

import { deepMerge } from '@/utils'

import type { RequestOptions, Result, CreateAxiosOptions } from './types'

import router from '@/router'

/**
 * @description: 数据处理,方便区分多种处理方式
 */
const transform: AxiosTransform = {
  /**
   * @description: 处理请求数据
   */
  transformRequestData: (res: AxiosResponse, options: RequestOptions) => {
    const { isTransformResponse, isReturnNativeResponse } = options
    const { code } = res.data
    switch (code) {
      case ResultEnum.SUCCESS:
        break
      case ResultEnum.TOKENLOSE:
        window.$message.error('登录过期,请重新登录')
        router.push('/login')
        break
      case ResultEnum.ACCOUNTLOSE:
        window.$message.error('账号已封禁,请重新登录')
        router.push('/login')
        break
      case ResultEnum.AUTHORITY:
        window.$message.error('权限不足,请联系管理员')
        router.push('/login')
        break
    }
    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
    if (isReturnNativeResponse) {
      return res
    }
    // 不进行任何处理,直接返回
    // 用于页面代码可能需要直接获取code,data,message这些信息时开启
    if (!isTransformResponse) {
      return res.data
    }

    if (!res) {
      // return '[HTTP] Request has no return value';
      throw new Error('请求出错,请稍候重试')
    }
    return res.data
  },

  // 请求之前处理config
  beforeRequestHook: (config) => {
    return config
  },

  /**
   * @description: 请求拦截器处理 (config, options)
   */
  requestInterceptors: (config, options) => {
    const store = useUserStore()
    const token = store.getToken
    if (token && (config as any)?.requestOptions?.withToken !== false) {
      // jwt token
      ;(config as any).headers.Authorization = options.authenticationScheme
        ? `${options.authenticationScheme} ${token}`
        : token
    }
    return config
  },

  /**
   * @description: 响应错误处理
   */
  responseInterceptorsCatch: (error: any) => {
    return Promise.reject(error?.data)
  }
}

function createAxios(opt?: Partial) {
  return new VAxios(
    deepMerge(
      {
        timeout: 10 * 1000,
        authenticationScheme: 'Bearer',
        headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
        // 数据处理方式
        transform,
        // 配置项,下面的选项都可以在独立的接口请求中覆盖
        requestOptions: {
          // 默认将prefix 添加到url
          joinPrefix: true,
          // 是否返回原生响应头 比如:需要获取响应头时使用该属性
          isReturnNativeResponse: false,
          // 需要对返回数据进行处理
          isTransformResponse: true,
          // post请求的时候添加参数到url
          joinParamsToUrl: false,
          // 格式化提交参数时间
          formatDate: true,
          // 消息提示类型
          errorMessageMode: 'none',
          //  是否加入时间戳
          joinTime: true,
          // 忽略重复请求
          ignoreCancelToken: true,
          // 是否携带token
          withToken: true
        },
        withCredentials: false
      },
      opt || {}
    )
  )
}

export const http = createAxios()

6.apit.ts中使用示范

// 存放全局请求辅助api
import { http } from '@/utils/http'
import type { BasicResponseModel } from '@/types/api'

/**
 * @description 获取所有角色
 * @returns
 */
export const getAllRoleListApi = () => {
  return http.request({
    url: '/role/list',
    method: 'get'
  })
}

/**
 * s@description 上传文件
 * @param data
 * @returns
 */
export const upLoadFileApi = (data: { file: File; data: { type: string } }) => {
  console.log(data)
  return http.uploadFile(
    {
      url: '/upload/picture'
    },
    {
      data: data.data,
      file: data.file
    }
  )
}

结语

觉得有帮助的小伙伴点个赞支持下

你可能感兴趣的:(前端,javascript,开发语言)