前端请求封装实战解析:基于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 替换为你的状态管理逻辑即可。

相关推荐
摸鱼的春哥7 分钟前
春哥的Agent通关秘籍07:5分钟实现文件归类助手【实战】
前端·javascript·后端
念念不忘 必有回响10 分钟前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
C澒16 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅17 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘19 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端