Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇

【Axios统一封装】+【中后台前端接口规范】:从拦截器配置到业务落地,彻底搞懂API请求规范封装,避开重复代码与分散错误处理坑!

📑 文章目录

  • 一、为什么要做统一封装?
    • [1.1 不封装时的问题](#1.1 不封装时的问题)
  • 二、封装前先搞清楚这几件事
    • [2.1 请求与响应的结构](#2.1 请求与响应的结构)
    • [2.2 常见后端约定](#2.2 常见后端约定)
  • 三、完整封装示例(可直接用于项目)
    • [3.1 目录结构建议](#3.1 目录结构建议)
    • [3.2 第一步:创建 request.js 封装](#3.2 第一步:创建 request.js 封装)
    • [3.3 第二步:按模块拆分 API](#3.3 第二步:按模块拆分 API)
    • [3.4 第三步:在页面中使用](#3.4 第三步:在页面中使用)
  • 四、常见配置说明
    • [4.1 baseURL 的几种写法](#4.1 baseURL 的几种写法)
    • [4.2 GET 用 params,POST 用 data](#4.2 GET 用 params,POST 用 data)
    • [4.3 请求头里传 Token 的常见方式](#4.3 请求头里传 Token 的常见方式)
  • 五、常见坑与注意事项
    • [5.1 拦截器里 return 的值](#5.1 拦截器里 return 的值)
    • [5.2 业务需要原始 response 时](#5.2 业务需要原始 response 时)
    • [5.3 某些接口不想统一错误提示](#5.3 某些接口不想统一错误提示)
    • [5.4 重复请求与取消](#5.4 重复请求与取消)
  • 六、小结:封装时建议遵守的原则
  • 七、速查清单
  • [🔍 系列模块导航](#🔍 系列模块导航)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、为什么要做统一封装?

1.1 不封装时的问题

日常直接用 Axios 时,代码常是这样:

js 复制代码
// 项目里到处都是这样的重复代码
axios.get('/api/user/info').then(res => {
  if (res.data.code === 200) {
    console.log(res.data.data)
  } else {
    Message.error(res.data.message || '请求失败')
  }
}).catch(err => {
  Message.error('网络错误,请稍后重试')
})

// 另一个文件里又来一遍
axios.post('/api/order/create', { productId: 1 }).then(res => {
  if (res.data.code === 200) {
    // ...
  } else {
    Message.error(res.data.message || '请求失败')
  }
}).catch(err => {
  Message.error('网络错误,请稍后重试')
})

问题主要有:

  1. 重复逻辑code === 200、错误提示、catch 在几十上百个请求里反复写。
  2. 风格不统一 :有人用 res.data,有人用 res.data.data,维护成本高。
  3. 扩展困难:加 token、统一 loading、请求重试等,都要改很多地方。
  4. 错误处理分散:401 未登录、403 无权限等,每个接口各自处理,容易遗漏。

统一封装的目的,就是把这些公共逻辑集中在一处,让业务代码更干净、可维护。

[⬆ 返回目录](#⬆ 返回目录)


二、封装前先搞清楚这几件事

2.1 请求与响应的结构

先看一次完整请求的"路径":

复制代码
你的代码 axios.get('/api/user')
    ↓
请求拦截器(可以改请求头、加 token 等)
    ↓
真正发出去的 HTTP 请求
    ↓
后端返回
    ↓
响应拦截器(可以统一处理 data、错误等)
    ↓
你的 .then() 里拿到的 res

业务里一般期望:

  • 只拿到「业务数据」,而不是 res.data.data 这种多层结构。
  • 错误在封装层统一处理,业务尽量只写「成功时做什么」。

[⬆ 返回目录](#⬆ 返回目录)

2.2 常见后端约定

很多项目都是类似约定:

js 复制代码
// 成功时
{
  code: 200,
  message: "成功",
  data: { id: 1, name: "张三" }
}

// 失败时
{
  code: 401,
  message: "未登录,请先登录"
  // 可能没有 data
}

code 含义可约定为:

  • 200:成功
  • 401:未登录
  • 403:无权限
  • 500:服务器错误

封装时围绕这些约定来设计,就能让接口调用更统一。

[⬆ 返回目录](#⬆ 返回目录)


三、完整封装示例(可直接用于项目)

3.1 目录结构建议

复制代码
src/
├── utils/
│   └── request.js      # Axios 封装
├── api/
│   ├── user.js         # 用户相关接口
│   └── order.js        # 订单相关接口

[⬆ 返回目录](#⬆ 返回目录)

3.2 第一步:创建 request.js 封装

js 复制代码
/**
 * Axios 统一封装
 * 职责:基础配置、拦截器、统一错误处理
 */
import axios from 'axios'
import { ElMessage } from 'element-plus'  // 如果用 Vue3 + Element Plus
// 如果用 Vue2,改为:import { Message } from 'element-ui'

// ============ 1. 创建 axios 实例 ============
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',  // Vue3 Vite 写法
  // baseURL: process.env.VUE_APP_BASE_API || '/api',   // Vue2 Vue CLI 写法
  timeout: 15000,  // 超时时间 15 秒
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

// ============ 2. 请求拦截器 ============
request.interceptors.request.use(
  (config) => {
    // 从本地存储获取 token(按你项目的存储方式改)
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => {
    console.error('请求拦截器出错:', error)
    return Promise.reject(error)
  }
)

// ============ 3. 响应拦截器 ============
request.interceptors.response.use(
  (response) => {
    const res = response.data

    // 根据你们后端的 code 约定来写
    if (res.code === 200) {
      // 成功:只返回业务数据,业务层直接用
      return res.data
    }

    // 业务错误(如参数错误、权限不足等)
    ElMessage.error(res.message || '请求失败')
    return Promise.reject(new Error(res.message || '请求失败'))
  },
  (error) => {
    // HTTP 错误(网络错误、404、500 等)
    if (error.response) {
      const status = error.response.status
      switch (status) {
        case 401:
          ElMessage.error('未登录或登录已过期,请重新登录')
          // 跳转登录页
          // router.push('/login')
          break
        case 403:
          ElMessage.error('没有权限访问')
          break
        case 404:
          ElMessage.error('请求的资源不存在')
          break
        case 500:
          ElMessage.error('服务器错误,请稍后重试')
          break
        default:
          ElMessage.error(error.response.data?.message || '请求失败')
      }
    } else {
      ElMessage.error('网络异常,请检查网络连接')
    }
    return Promise.reject(error)
  }
)

export default request

要点说明:

  • axios.create():创建独立实例,不影响全局 Axios。
  • baseURL:所有请求前都会自动拼接,不用在每次调用里写 /api
  • timeout:超时统一处理,避免请求一直挂起。
  • 请求拦截器:统一加 token、统一请求头。
  • 响应拦截器:code === 200 时直接返回 res.data,业务层少一层嵌套;非 200 和 HTTP 错误统一提示和 reject

[⬆ 返回目录](#⬆ 返回目录)

3.3 第二步:按模块拆分 API

js 复制代码
// api/user.js
import request from '@/utils/request'

/**
 * 获取用户信息
 * @returns {Promise} 用户信息对象
 */
export function getUserInfo() {
  return request({
    url: '/user/info',
    method: 'get'
  })
}

/**
 * 更新用户资料
 * @param {Object} data - 用户资料
 */
export function updateUserInfo(data) {
  return request({
    url: '/user/update',
    method: 'post',
    data
  })
}
js 复制代码
// api/order.js
import request from '@/utils/request'

/**
 * 创建订单
 * @param {Object} data - 订单数据
 */
export function createOrder(data) {
  return request({
    url: '/order/create',
    method: 'post',
    data
  })
}

/**
 * 获取订单列表
 * @param {Object} params - 查询参数
 */
export function getOrderList(params) {
  return request({
    url: '/order/list',
    method: 'get',
    params
  })
}

[⬆ 返回目录](#⬆ 返回目录)

3.4 第三步:在页面中使用

js 复制代码
// views/UserProfile.vue
<script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo, updateUserInfo } from '@/api/user'

const userInfo = ref({})

onMounted(async () => {
  try {
    // 直接拿到业务数据,不需要 res.data.data
    userInfo.value = await getUserInfo()
  } catch (err) {
    // 错误已在拦截器里提示,这里一般只需做页面级处理(如清空、跳转)
    console.log('获取用户信息失败', err)
  }
})

async function handleSave() {
  try {
    await updateUserInfo(userInfo.value)
    ElMessage.success('保存成功')
  } catch (err) {
    // 错误已统一处理
  }
}
</script>

对比不封装时:

  • 不需要再写 if (res.data.code === 200)
  • 不需要在 .catch 里重复弹错误。
  • 接口路径、环境变量都在封装层统一管理。

[⬆ 返回目录](#⬆ 返回目录)


四、常见配置说明

4.1 baseURL 的几种写法

js 复制代码
// 1. 开发用代理,生产用真实域名
baseURL: import.meta.env.VITE_API_BASE_URL  // 在 .env.development 里配 /api

// 2. 根据环境自动切换
baseURL: import.meta.env.MODE === 'development' ? '/api' : 'https://api.xxx.com'

// 3. 多环境
// .env.development  -> VITE_API_BASE_URL=/api
// .env.production   -> VITE_API_BASE_URL=https://api.xxx.com

开发时通常配合 Vite/Vue CLI 的 proxy,把 /api 转发到后端:

js 复制代码
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  }
}

[⬆ 返回目录](#⬆ 返回目录)

4.2 GET 用 params,POST 用 data

js 复制代码
// GET:参数拼在 URL 上 ?id=1&name=xxx
request({
  url: '/user/list',
  method: 'get',
  params: { id: 1, name: '张三' }
})

// POST:参数在请求体里
request({
  url: '/user/create',
  method: 'post',
  data: { name: '张三', age: 25 }
})

混用会出错:GET 用 data 不会出现在 URL 上,POST 用 params 一般也不会被后端按请求体解析。

[⬆ 返回目录](#⬆ 返回目录)

4.3 请求头里传 Token 的常见方式

js 复制代码
// 1. Bearer Token(最常见)
config.headers.Authorization = `Bearer ${token}`

// 2. 自定义字段
config.headers['X-Token'] = token

// 3. 业务要求的字段名
config.headers['Authorization'] = token  // 有的后端不要 Bearer 前缀

以你们后端文档为准。

[⬆ 返回目录](#⬆ 返回目录)


五、常见坑与注意事项

5.1 拦截器里 return 的值

js 复制代码
// ✅ 正确:成功时 return 的值会作为 Promise 的 resolve 结果
request.interceptors.response.use(
  (response) => {
    return response.data  // 业务拿到的就是这个
  }
)

// ❌ 容易出错:忘了 return
request.interceptors.response.use(
  (response) => {
    response.data  // 没有 return,业务拿到的是 undefined
  }
)

[⬆ 返回目录](#⬆ 返回目录)

5.2 业务需要原始 response

例如要读 headers 里的分页信息:

js 复制代码
// 方案一:在接口里传一个配置,告诉拦截器「不要解包」
// request.js 中
request.interceptors.response.use((response) => {
  const res = response.data
  if (res.code === 200) {
    if (response.config.returnRawResponse) {
      return response  // 返回完整 response
    }
    return res.data
  }
  // ...
})

// 调用时
request({
  url: '/order/list',
  method: 'get',
  params: { page: 1 },
  returnRawResponse: true  // 自定义标记
})

[⬆ 返回目录](#⬆ 返回目录)

5.3 某些接口不想统一错误提示

js 复制代码
// 在 request.js 的响应拦截器中
if (res.code !== 200) {
  if (!response.config.skipErrorHandler) {
    ElMessage.error(res.message || '请求失败')
  }
  return Promise.reject(new Error(res.message || '请求失败'))
}

// 调用时
request({
  url: '/some/silent-api',
  method: 'get',
  skipErrorHandler: true  // 不弹出错误提示
})

[⬆ 返回目录](#⬆ 返回目录)

5.4 重复请求与取消

用户快速点击时可能发多次相同请求,可以配合 AbortController 或请求去重逻辑,在拦截器里统一处理,这里不展开,但封装时要预留扩展点。

[⬆ 返回目录](#⬆ 返回目录)


六、小结:封装时建议遵守的原则

原则 说明
单一职责 request.js 只做请求封装,业务逻辑放 API 和页面
统一入口 所有接口都通过封装的 request,不直接 axios.xxx
约定优于配置 codedata 结构统一,减少分支判断
可配置 通过 config 支持 skipErrorHandlerreturnRawResponse
环境分离 baseURLtimeout 等用环境变量,便于多环境部署

[⬆ 返回目录](#⬆ 返回目录)

七、速查清单

  • 是否所有接口都通过封装的 request 调用?
  • baseURL 是否按环境正确配置?
  • Token 是否在请求拦截器里统一添加?
  • 成功响应是否统一返回 data,减少业务层解包?
  • 401、403、500 等是否都有统一处理?
  • 特殊接口(静默失败、需要原始响应)是否通过配置支持?

按以上方式封装后,日常写业务时只需关心「调哪个接口、传什么参数」,其余都交给封装层,代码会更清晰、可维护。

[⬆ 返回目录](#⬆ 返回目录)

🔍 系列模块导航

📝 API与异步请求规范

一、《Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇》
二、《Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇》
三、《Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇》
四、《前端实战:Excel 导入导出规范(命名 + 校验 + 错误处理 + 统一交互)|API 与异步请求规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。

更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
Alan Lu Pop1 小时前
Figma 配置
前端·ai编程·cursor
Moment1 小时前
手把手搭一套前端监控采集 SDK
前端·javascript·面试
华洛1 小时前
实战指南:企业如何选择AI需求的落地技术方案
前端·产品经理·产品
莫爷1 小时前
JSON vs XML vs YAML 深度对比:如何选择合适的数据格式?
xml·前端·json
We་ct1 小时前
LeetCode 33. 搜索旋转排序数组:O(log n)二分查找
前端·算法·leetcode·typescript·个人开发·二分·数组
华仔啊2 小时前
前端不懂 Java?后端怕 CSS?这套AI全栈方案专治各种偏科
java·前端·后端
木斯佳2 小时前
前端八股文面经大全:得物AI应用开发一面(2026-03-23)·面经深度解析【加精】
前端·人工智能·ai·markdown·chat·rag
无巧不成书02184 小时前
Windows PowerShell执行策略详解:从npm报错到完美解决
前端·windows·npm·powershell执行策略·执行策略·npm.ps1·脚本报错
Z兽兽10 小时前
React@18+Vite项目配置env文件
前端·react.js·前端框架