vue3+ts+axios封装添加返回类型推断

814 阅读3分钟

初学ts,折腾了一下午,终于把返回值推断弄出来了. 封装请看 一篇文章带你详细了解axios的封装

主要思路是改造AxiosInstance,来源如何在axios拦截器中将类型向后传递? - 峎族的回答 - 知乎

下面直接上代码

axios封装文件

import axios, {
  // AxiosInstance,
  AxiosResponse,
  AxiosRequestConfig,
  InternalAxiosRequestConfig,
} from 'axios'
import { ElMessage, ElLoading } from 'element-plus'
// 返回值类型
export interface ResponseData<T = any> {
  code?: number
  msg?: string
  token?: string
  data?: T
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface Response<T = any> {
  data: ResponseData
  config: InternalAxiosRequestConfig
}
export interface AxiosInstance {
  <T = any>(config: AxiosRequestConfig): Promise<T>
  request<T = any>(config: AxiosRequestConfig): Promise<T>
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  post<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig,
  ): Promise<T>
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
  patch<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig,
  ): Promise<T>
  interceptors: any
}

// 核心在这里,改造AxiosInstance使之返回我们想要的类型
// 我的接口固定返回ResponseData
// ResponseData里面的data又可以由调用接口时传入
export interface IAxiosInstance extends AxiosInstance {
  <D = any>(config: AxiosRequestConfig): Promise<ResponseData<D>>
}

const loadingInstance = ElLoading.service
let requestCount = 0
const showLoading = () => {
  requestCount++
  if (requestCount === 1) loadingInstance()
}
const closeLoading = () => {
  requestCount--
  if (requestCount === 0) loadingInstance().close()
}

const service: IAxiosInstance = axios.create({
  method: 'get',
  baseURL: import.meta.env.VITE_APP_BASE_API,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
  },
  timeout: 10000,
})
//请求拦截

declare module 'axios' {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface InternalAxiosRequestConfig<D = any, T = any> {
    loading?: boolean
    isToken?: boolean
  }
}
declare module 'axios' {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface AxiosRequestConfig<D = any> {
    loading?: boolean
    isToken?: boolean
  }
}

const requestMap = new Map()
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController()
    const key = config.data + config.url
    config.signal = controller.signal
    if (requestMap.has(key)) {
      requestMap.get(key).abort()
      requestMap.delete(key)
    } else {
      requestMap.set(key, controller)
    }
    // console.log(123);

    const { loading = true, isToken = true } = config

    if (loading) showLoading()
    if (localStorage.getItem('token') && !isToken) {
      config.headers['Authorization'] =
        'Bearer ' + localStorage.getItem('token') // 让每个请求携带自定义token 请根据实际情况自行修改
    }

    return config
  },
  (error) => {
    console.log(error)
  },
)

service.interceptors.response.use(
  (res: AxiosResponse<any, Response<ResponseData>>) => {
    const { data, config } = res
    const { loading = true } = config
    if (loading) closeLoading()
    if (data.code != 200) {
      switch (data.code) {
        case 500:
          ElMessage({
            message: data.msg,
            type: 'error',
          })
          break
        default:
          ElMessage({
            message: data.msg ?? '请求失败',
            type: 'error',
          })
      }

      if (data.code === 401) {
        //登录状态已过期.处理路由重定向
        console.log('loginOut')
      }
      throw new Error(data?.msg ?? '请求失败')
    }
    return data
  },
  (error) => {
    closeLoading()
    let { message } = error
    if (message == 'Network Error') {
      message = '后端接口连接异常'
    } else if (message.includes('timeout')) {
      message = '系统接口请求超时'
    } else if (message.includes('Request failed with status code')) {
      message = '系统接口' + message.substring(message.length - 3) + '异常'
    }
    ElMessage({
      message: message,
      type: 'error',
    })
    return Promise.reject(error)
  },
)


export const request = service

调用接口


import { request } from '@/utils/service'

interface IUserRequestData {
  username: string
  password: string
}

// 生成tag列表interface
export interface TagList {
  color?: string
  creator?: string
  name?: string
  _id?: string
}
/** 登录,返回 token */
export function accountLogin(data: IUserRequestData) {
  return request({
    url: 'users/login',
    method: 'post',
    data,
  })
}

/** 获取tag列表 */
export function tagList(params?: any) {
  return request<TagList[]>({
    url: 'tags/list',
    method: 'get',
    params,
  })
}

思考了一下,不替代原来的AxiosInstance,另一种方法:

//axios封装文件中
// 其他地方与上述一样
// 这里原样引入AxiosInstance,不改造
import axios, {
  AxiosInstance,
} from 'axios'

//这里依旧
export interface IAxiosInstance extends AxiosInstance {
  <D = any>(config: AxiosRequestConfig): Promise<ResponseData<D>>
}

const service: IAxiosInstance = axios.create({
  method: 'get',
  baseURL: import.meta.env.VITE_APP_BASE_API,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
  },
  timeout: 10000,
})


//调用的时候request后面必须加上<>才能导向IAxiosInstance实例
// 没有规定类型的时候使用any,有类型使用自己的类型
/** 登录,返回 token */
export function accountLogin(data: IUserRequestData) {
  return request<any>({
    url: 'users/login',
    method: 'post',
    data,
  })
}

/** 获取tag列表 */
export function tagList(params?: any) {
  return request<TagList[]>({
    url: 'tags/list',
    method: 'get',
    params,
  })
}

调用的时候可以看到类型提示了

更新

// request.ts
这里使用Omit过滤掉AxiosInstance里面的request方法后,请求就会走下面的方法
export interface IAxiosInstance extends Omit<AxiosInstance, 'request'> {
  <T = any, R = ResponseData<T>, D = any>(
    config: AxiosRequestConfig<D>,
  ): Promise<R>
  /**
   * @description 用于请求数据
   * @param T 指定返回值类型
   */
  <T = any>(config?: AxiosRequestConfig): Promise<ResponseData<T>>
  /**
   * @description 用于请求数据
   * @param T 指定返回值类型
   * @param D 指定请求值类型
   */
  <T = any, D = any>(config: AxiosRequestConfig<D>): Promise<ResponseData<T>>
}

// user调用

/** 登录,返回 token */
// 直接request就行,不用加上request<any>
export function accountLogin(data: IUserRequestData) {
  return request({
    url: 'users/login',
    method: 'post',
    data,
  })
}

uTools_1686308969470.png