基于TS的axios封装 :轻松上手实战

2,543 阅读3分钟

1、需求

在项目搭建阶段,基本上都有一个对请求拦截封装的request文件有以下作用:

  1. 请求时进行同一拦截,统一添加请求参数或者header如token或者对参数进行加密处理,格式化处理等
  2. 获取接口响应时对结果进行统一的拦截如接口错误,请求超时,用户登录过期等

然后就是对接口进行统一管理的API文件,用于编写接口的请求函数,请求方法,路径等,方便统一维护。

具体涉及文件:request.js API文件夹下api.js

2.问题

传统的JS封装方法,我们无法推断接口请求函数的入参出参,不利于大项目里的系统维护,也不符合TS的编码规范,所以需求修改以上文件的编写,达到以下要求:

  1. 拥有原本的全局拦截能力
  2. 能够推导接口的入参和出参的数据类型

3.request.ts 封装

import router from '@/router'
import { store } from '@/store'
import axios, { AxiosRequestConfig } from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL
})

// request 不支持泛型
// request.get post put 支持响应数据泛型
// 由于我们的后端又包装了一层数据data ,导致访问数据比较麻烦,所以又封装一层
// 控制登录过期的锁
let isRefreshing = false
// 请求拦截器
request.interceptors.request.use((config:AxiosRequestConfig) => {
  // 请求头添加token
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = token
  }
  return config
}, error => {
  return Promise.reject(error)
})

// 响应拦截器
request.interceptors.response.use(response => {
  const status = response.data.status
  if (!status || status === 200) {
    return response
  }
  if (status === 401) {
    // 防止多次弹出登录框
    if (isRefreshing) return Promise.reject(response)
    // 清除本地过去的登录状态
    ElMessageBox.confirm('登录状态已过期,请重新登录', '提示', {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      // 跳转到登录页
      store.commit('setUser', null)
      router.push({
        name: 'login',
        query: {
          // 方便登录成功后回到当前页面
          redirect: router.currentRoute.value.fullPath
        }
      })
    }).catch(() => {
      // 抛出异常
    }).finally(() => {
      isRefreshing = false
    })
    // 在内部消化掉这个业务异常
    return Promise.reject(response)
  }

  // 其他错误情况
  ElMessage.error(response.data.msg || '请求失败,请稍后重试')
  return Promise.reject(response.data)
}, error => {
  return Promise.reject(error)
})

// 通过导出自定的request实例,来实现对axios的类型封装
export default <T = any>(config:AxiosRequestConfig) => {
  return request(config).then(res => {
    return (res.data.data || res.data) as T
  })
}

4.API 文件编写

1. 文件结构

image.png

2. types/admin.ts 类型声明

// 请求参数
export interface IListParams {
  page: number,
  limit: number,
  name: string,
  roles: string,
  status: 0 | 1 | ''
}
// 响应数据对象
export interface Admin {
  id: number,
  account: string,
  head_pic: string,
  pwd: string,
  real_name: string,
  roles: string,
  last_ip: string,
  last_time: number,
  add_time: number,
  login_count: number,
  level: number,
  status: number,
  is_del: number,
  _add_time: string,
  _last_time: string,
}
// 添加/修改时传入对象
export type IAdminPostData = Pick<Admin, 'account' | 'pwd' | 'real_name' | 'roles' | 'status'> & { conf_pwd: string }

3.admin.ts 接口文件

import request from '@/utils/request'
import { Admin, IListParams, IAdminPostData } from './types/admin'

// 获取管理员列表
export function getAdmins (params: IListParams) {
  return request<{
    count: number,
    list: Admin[]
  }>({
    url: '/seting/admin',
    method: 'get',
    params
  })
}
// 增加管理员
export function createAdmin (data: IAdminPostData) {
  return request({
    url: '/seting/admin',
    method: 'post',
    data
  })
}
// 修改管理员
export function updateAdmin (id: number, data: IAdminPostData) {
  return request({
    url: `/seting/admin/${id}`,
    method: 'put',
    data
  })
}
// 删除管理员
export function deleteAdmin (id: number) {
  return request({
    url: `/seting/admin/${id}`,
    method: 'delete'
  })
}
// 修改管理员状态
export function updateAdminStatus (id: number, status: number) {
  return request({
    url: `/seting/admin/${id}/${status}`,
    method: 'put'
  })
}

接口请求使用

此时在接口函数调用的时候就可以看见需要的参数以及接口返回的数据了

image.png

当我们拿到结果时,vscode也会提示

image.png

此时就完成接口请求的封装了,后续接口都可以参照这个流程开发。

总结

使用TS在前期确实会加大你的工作量,需要编写很多类型声明代码,但不可否定的是,TS能够是系统更具有稳定性,减少BUG的产生以及增加其维护性,更方便的做交接等。
在前期做好接口声明,也能够减少在开发时接口对接中出现错误。