前端请求封装实战解析:基于Axios的封装技巧

引言

每次在VSCode里看到项目中那些赤裸裸的axios请求调用,你是不是也和我一样浑身难受?就像穿着睡衣见客户------虽然能用,但总少了点专业感。今天,我要带你给axios穿上得体的"西装"!

想象一下这些日常开发中的糟心场景:

  • 接口返回401时页面没有任何提示,用户操作突然卡死
  • 每个请求都要手动加loading,代码里充斥着重复的ElLoading.service()
  • 后端突然返回一个神秘错误码,前端一脸懵逼不知道该怎么提示用户
  • 提交表单时手抖多点几次,结果数据库里冒出几条重复数据

作为一个经历过这些痛苦的老司机,我决定把我的"生存经验"分享给你。这不是那种教科书式的封装教程,而是一个真正从实战中摸爬滚打出来的解决方案。我们将用TypeScript+Vue3+Element Plus的组合,打造一个请求库:

正文


一、创建Axios实例:你的HTTP请求"管家"

在项目中直接使用 axios.get()axios.post() 虽然方便,但就像租房不签合同------短期能用,长期必出问题。我们需要一个专属定制的Axios实例,来统一管理所有API请求的"家务事"。

1. 基础配置:给请求一个"家"

typescript 复制代码
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 环境变量配置的API根路径
  timeout: 10000 // 10秒超时
})
  • baseURL:通过环境变量动态配置,区分开发/生产环境,避免硬编码
  • timeout:10秒超时是经过验证的平衡值(短了影响大请求,长了用户以为卡死)

💡 Pro Tip :别在代码里写死baseURL!用import.meta.env配合.env文件管理,让不同环境自动切换地址。

2. 默认请求头:设置"着装规范"

typescript 复制代码
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
  • 统一设置Content-Type,避免后端收到undefined时一脸懵逼
  • 个别特殊请求(如文件上传)可以在具体请求中覆盖这个配置

3. 为什么不用默认实例?

直接修改axios.defaults会影响全局,就像在合租房的公共区域乱堆杂物------其他第三方库的请求可能被意外影响。创建一个独立实例能保证:

  • ✅ 隔离性:项目请求配置不会污染全局
  • ✅ 可扩展性:后续可创建多个实例(如专门处理文件上传的实例)
  • ✅ 可维护性:所有自定义配置集中管理

4. 环境变量安全提示

typescript 复制代码
// .env.development
VITE_APP_BASE_API = '/api' // 开发环境代理

// .env.production  
VITE_APP_BASE_API = 'https://api.your-domain.com' // 生产环境真实地址
  • 使用VITE_前缀暴露变量(Vite特性)
  • 敏感信息不要前端配置,应该通过后端动态获取

关键点总结

🔹 创建独立实例避免全局污染

🔹 baseURL通过环境变量动态注入

🔹 默认超时和请求头是基础保障

🔹 像对待租房合同一样认真对待Axios初始配置

在下一部分,我们将深入请求拦截器,看看如何给每个请求"化妆打扮"再出门------包括自动携带Token、防重复提交等高级技巧。


二、请求拦截器:给每个请求"化妆"再出门

如果说创建实例是给axios安了个家,那么请求拦截器就是每次出门前的精致打扮。让我们拆解这段"化妆术"的每个关键步骤:

1. Token管理:你的请求"身份证"

typescript 复制代码
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
  config.headers['Authorization'] = 'Bearer ' + getToken()
}
  • getToken():这是一个自定义函数(通常从localStorage/cookie读取)
  • 如何适配你的项目
    • 如果不用JWT,改成你们团队的认证方式(如Basic Auth)
    • 不需要认证的接口,在请求时添加headers: { isToken: false }
  • 为什么用=== false
    这是一个防御性编程技巧,确保只有当显式设置为false时才跳过token,避免undefined导致的意外行为

2. 防重复提交:防止用户"手抖"

typescript 复制代码
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
    const requestObj = {
      url: config.url,
      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
      time: new Date().getTime()
    }
    // 计算请求唯一标识的字符串长度,用于避免缓存过大数据
    const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
    const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
    if (requestSize >= limitSize) {
      console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
      return config
    }
    const sessionObj = cache.session.getJSON('sessionObj')
    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
      cache.session.setJSON('sessionObj', requestObj)
    } else {
      const s_url = sessionObj.url                // 请求地址
      const s_data = sessionObj.data              // 请求数据
      const s_time = sessionObj.time              // 请求时间
      const interval = 1000                       // 间隔时间(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
        const message = '数据正在处理,请勿重复提交'
        console.warn(`[${s_url}]: ` + message)
        return Promise.reject(new Error(message))
      } else {
        cache.session.setJSON('sessionObj', requestObj)
      }
    }
  }
  • 为什么需要防重复
    • 用户双击提交按钮
    • 网络延迟导致重复请求
    • 可能引发数据库重复数据或支付重复扣款
  • Object.keys(JSON.stringify(requestObj)).length
    计算请求体大小,防止大文件占用过多内存(5MB限制是经验值),浏览器 sessionStorage 通常限制为 5-10MB

3. GET请求参数处理

typescript 复制代码
if (config.method === 'get' && config.params) {
  config.url = config.url + '?' + tansParams(config.params) // tansParams是自定义参数序列化函数
}
  • **tansParams**的作用:
    将对象{ page: 1, size: 10 }转成page=1&size=10格式
    (你的项目可能需要换成qs.stringify或自定义逻辑)

4. 拦截器 vs 普通请求器

特性 拦截器 普通请求器
执行时机 所有请求前后自动触发 手动调用时执行
使用场景 全局逻辑(如Token、Loading) 具体业务请求
修改请求 可修改config 只能传递初始参数

5. 关键问题解答

Q:为什么限制请求数据大小?

A:防止超大请求体导致:

  • 内存溢出(OOM)
  • 防重复提交的缓存失效
  • 服务器拒绝处理(如Nginx默认限制1MB)

Q:如何在自己的项目实现防重复?

A:可以简化为:

typescript 复制代码
// 简易版实现(基于URL+参数指纹)
const requestKey = `${config.url}_${JSON.stringify(config.data)}`
if (cache.session.get(requestKey)) {
  return Promise.reject('请勿重复提交')
}
cache.session.set(requestKey, true, 1000) // 1秒后自动释放

Q:为什么用(config.headers || {})

A:防御config.headers未定义的情况,等效于:

typescript 复制代码
const headers = config.headers ? config.headers : {}

拦截器改造指南

🔸 替换getToken()为你们项目的认证方式

🔸 根据后端API规范调整参数序列化逻辑(如时间格式处理)

🔸 防重复时间间隔(interval)按业务需求调整

🔸 超大文件上传需要特殊处理(建议走单独实例)

在下一部分,我们将解剖响应拦截器------这里才是真正见证"奇迹"的地方,包括自动错误处理、Token过期跳转等核心逻辑。


三、响应拦截器:请求归来后的"质检流水线"

如果说请求拦截器是精心包装快递,那么响应拦截器就是拆包裹时的验货过程。这里藏着让前端优雅处理各种异常的终极秘密!

1. 二进制响应:特殊通道处理

typescript 复制代码
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
  return res.data // 直接放行二进制流
}
  • 适用场景:Excel导出、PDF预览、图片下载等
  • 为什么特殊处理:二进制数据不能被JSON解析,需要"绿色通道"

2. 状态码质检流水线

(1) 401过期处理:优雅重新登录
typescript 复制代码
if (code === 401) {
  if (!isRelogin.show) {
    isRelogin.show = true
    ElMessageBox.confirm('登录过期,请重新登录', '提示', {
      confirmButtonText: '重新登录',
      cancelButtonText: '取消'
    }).then(() => {
      useUserStore().logOut().then(() => {
        location.href = '/login?redirect=' + encodeURIComponent(route.fullPath)
      })
    })
  }
  return Promise.reject('会话过期')
}
  • 关键设计
    • isRelogin全局锁防止弹窗重复
    • 跳转登录页携带当前路由,登录后自动返回
    • 比直接跳登录页体验提升100%
(2) 500服务器错误:精准定位
typescript 复制代码
else if (code === 500) {
  ElMessage({ message: msg, type: 'error' })
  return Promise.reject(new Error(msg))
}
  • 为什么单独处理:服务器内部错误通常需要特殊记录
  • msg来源 :优先使用预定义的errorCode,其次用后端返回消息
(3) 业务异常:601警告提示
typescript 复制代码
else if (code === 601) {
  ElMessage({ message: msg, type: 'warning' }) // 黄色温和提示
  return Promise.reject(new Error(msg))
}
  • 适用场景:余额不足、操作限制等业务异常
  • 与500的区别:不需要红色恐慌提示

3. 网络异常处理:给用户看得懂的人话

typescript 复制代码
error => {
  let { message } = error
  if (message == "Network Error") {
    message = "网络连接异常,请检查网络"
  } else if (message.includes("timeout")) {
    message = "请求超时,请重试"
  }
  ElMessage({ message, type: 'error', duration: 5000 })
}
  • 翻译策略
    • Network Error → 网络连接问题
    • timeout → 请求超时
    • status code → 具体接口异常
  • 为什么重要:用户看不懂英文技术报错!

4. 核心问题解答

Q:blobValidate是什么?

A:自定义的Blob校验函数,典型实现:

typescript 复制代码
function blobValidate(data) {
  return data instanceof Blob && data.size > 0
}

Q:为什么用Promise.reject传递错误?

A:保持错误冒泡能力,让业务代码可以try/catch.catch()捕获

Q:如何扩展自定义错误码?

A:在errorCode.js中添加映射,比如:

javascript 复制代码
export default {
  401: "未授权",
  403: "禁止访问",
  1001: "商品库存不足" // 业务自定义代码
}

5. 拦截器改造Checklist

  • 🔹 根据业务补充错误码处理(如402支付相关)
  • 🔹 国际化:将中文提示换成i18n多语言
  • 🔹 监控埋点:在错误分支添加Sentry监控
  • 🔹 重试机制:对网络错误可添加自动重试逻辑

响应拦截器设计精髓

✅ 分层处理:二进制、HTTP错误、业务错误各走不同通道

✅ 人话提示:把技术术语翻译成用户能理解的语言

✅ 可扩展性:通过errorCode轻松扩展新错误类型

✅ 体验优化:401处理保留返回路径,提升用户留存率

至此,一个企业级Axios封装的核心骨架已经完成。接下来你可以:

  1. 添加请求缓存功能
  2. 实现自动化Mock适配
  3. 集成TypeScript类型提示
  4. 添加性能监控埋点

最终效果:你的API调用会像这样优雅:

typescript 复制代码
api.getUserInfo()
  .then(data => { /* 业务处理 */ })
  .catch(err => { /* 统一错误已处理,这里只处理特殊逻辑 */ })

最后再补充一下,有些人可能不是很了解config,我来给大家介绍一下config,config 是 axios 自带的配置对象,只要你引入了 axios,就能直接用

是 axios 原生提供的核心配置对象 ,就像给每个 HTTP 请求准备的"定制化工具箱"。当你调用 axios(config)axios.get(url, config) 时,这个对象决定了请求的所有行为。

** 原生 vs 自定义 Config**

类型 来源 示例参数 特点
原生配置 axios 内置 url, method, timeout 所有 axios 版本通用
自定义配置 项目/团队扩展 isToken, repeatSubmit 需要拦截器配合处理

3. 关键原生配置详解

这些是官方文档中最常用的配置项:

js 复制代码
{
  // 核心配置
  url: '/user',          // 请求路径(必填)
  method: 'get',         // HTTP方法(默认get)
  baseURL: '/api',       // 自动拼接到url前
  
  // 数据相关
  params: { id: 1 },     // GET参数(拼接到URL)
  data: { name: 'Jack' },// POST请求体
  
  // 网络控制
  timeout: 5000,         // 超时时间(ms)
  responseType: 'json',  // 响应格式(json/blob等)
  
  // 浏览器相关
  withCredentials: true, // 跨域携带cookie
  xsrfCookieName: 'XSRF-TOKEN' // CSRF防护
}

总结

一句话总结:

这个axios封装通过拦截器自动处理了token、错误提示、防重复提交,让你用起来像点外卖一样简单------只管说要什么(发请求),剩下的包装配送(鉴权/loading/错误处理)全自动搞定!

三要素:

  1. 省心 - 自带Token管理,无需手动处理
  2. 安全 - 自动防重复提交,防止手抖连点
  3. 友好 - 统一错误提示,用户看到白话文报错

调用示例:

js 复制代码
// 只要关心业务参数,其他全托管
request.post('/order', { id: 123 }) 

就像外卖APP下单,你只管选菜品,平台负责配送/售后所有脏活累活 🚀。

本封装的核心逻辑(axios实例、拦截器)是框架无关的。若用于React或原生JS项目,只需将其中的 ElMessage 替换为对应的UI组件(如Ant Design的 message),并将 useUserStore 替换为你的状态管理逻辑即可。

相关推荐
web前端1232 分钟前
# 多行文本溢出实现方法
前端·javascript
文心快码BaiduComate2 分钟前
早期人类奴役AI实录:用Comate Zulu 10min做一款Chrome插件
前端·后端·程序员
人间观察员4 分钟前
如何在 Vue 项目的 template 中使用 JSX
前端·javascript·vue.js
布列瑟农的星空6 分钟前
大话设计模式——多应用实例下的IOC隔离
前端·后端·架构
EndingCoder11 分钟前
安装与环境搭建:准备你的 Electron 开发环境
前端·javascript·electron·前端框架
蓝银草同学27 分钟前
前端离线应用基石:深入浅出 IndexedDB 完整指南
前端·indexeddb
龙在天37 分钟前
什么是SourceMap?有什么作用?
前端
雪中何以赠君别42 分钟前
Vue 2 与 Vue 3 双向绑定 (v-model) 区别详解
前端·javascript·vue.js
林太白44 分钟前
Vue3-ElementPlus使用
前端·javascript·vue.js
Juchecar1 小时前
npm、pnpm、yarn 是什么?该用哪个?怎么用?如何迁移?
前端·node.js