Vue + Axios 从入门到封装:拦截器、错误处理、请求取消、接口管理全搞定

一、先搞清楚 Axios 是什么

浏览器自带的 fetch 能发请求,但写起来比较原始:要手动判断 response.ok,要手动转换 JSON,还没有请求/响应拦截这种高级货。Axios 是一个第三方库,帮我们把请求这件事变得特别省心:

  • 自动转换 JSON 数据

  • 可以在请求发出前和收到响应后统一处理(拦截器)

  • 支持取消请求

  • 支持请求超时设置

  • 支持请求重试(配合插件)

用人话讲:Axios 就是给 fetch 包了一层高级皮,让你写更少的代码,干更多的事。


二、在 Vue 项目里装上 Axios

打开你的项目终端,敲一行命令:

bash

复制代码
npm install axios

装完之后,就能在组件里直接 import 用。但千万别在每一个组件里都写一遍 axios.get(...),那样以后要改配置得改几百个文件,会死人。我们需要把它封装成一个统一的"请求工具"。


三、封装第一步:创建 Axios 实例

src 下面新建一个 utils/request.js,专门管理 Axios 实例。

javascript

复制代码
// src/utils/request.js
import axios from 'axios'

// 创建一个 Axios 实例,就像创建一个“专属的网络助手”
const request = axios.create({
  // baseURL:所有请求都会自动在前面加上这个地址
  // 开发环境用本地代理,或者直接写你的后端地址
  baseURL: 'http://localhost:3000/api',
  
  // timeout:请求超时时间,超过 10 秒还没响应就自动放弃
  timeout: 10000,
  
  // headers:每次请求默认带上的请求头
  headers: {
    'Content-Type': 'application/json'
  }
})

export default request

解释:

  • axios.create() 就像克隆一个新 axios,可以有自己的默认配置,不会影响全局。

  • baseURL 让你以后写 /user/login 就行,不用每次写完整地址。

  • timeout 防止一个请求卡死半天没反应,用户体验更好。


四、请求拦截器:在请求发出前做点事

很多时候我们需要在请求头里带上 token,告诉后端"我是谁"。如果每个接口都手动加,太蠢了。用请求拦截器,统一在发送前加上。

javascript

复制代码
// src/utils/request.js(接上面)

// 添加请求拦截器
request.interceptors.request.use(
  (config) => {
    // config 就是这次请求的所有配置信息,你可以在这里修改它
    
    // 从 localStorage 里取出 token(假设登录后存在那)
    const token = localStorage.getItem('token')
    
    // 如果 token 存在,就在请求头里加上 Authorization 字段
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    
    // 最后一定要把 config 返回,不然请求发不出去
    return config
  },
  (error) => {
    // 请求出错时的处理(一般不会走到这里)
    console.error('请求发出失败:', error)
    return Promise.reject(error)
  }
)

重点: config.headers.Authorization 这种写法是约定俗成的,后端会从这个字段里拿 token 验证身份。


五、响应拦截器:统一处理返回数据

后端返回的数据一般都有固定格式,比如 { code: 200, data: {...}, message: '成功' }。如果每次请求完都判断 code,又累又容易漏。用响应拦截器统一处理。

javascript

复制代码
// src/utils/request.js(接上面)

// 添加响应拦截器
request.interceptors.response.use(
  (response) => {
    // 响应状态码是 2xx 时进入这里
    // response.data 是后端返回的实际数据
    
    const res = response.data
    
    // 根据约定的后端返回码处理
    if (res.code === 200) {
      // 成功,直接返回 data,组件里就不用每次都取 .data 了
      return res.data
    } else if (res.code === 401) {
      // token 过期或未登录,跳转到登录页
      window.location.href = '/login'
      return Promise.reject(new Error(res.message || '登录已过期'))
    } else {
      // 其他业务错误,给个提示
      alert(res.message || '请求失败')
      return Promise.reject(new Error(res.message))
    }
  },
  (error) => {
    // 响应状态码不是 2xx 时进入这里(比如 404、500)
    if (error.response) {
      const status = error.response.status
      switch (status) {
        case 404:
          alert('请求的资源不存在')
          break
        case 500:
          alert('服务器错误,请稍后再试')
          break
        default:
          alert(`请求失败,状态码:${status}`)
      }
    } else if (error.code === 'ECONNABORTED') {
      // 超时的错误码是 ECONNABORTED
      alert('请求超时,请检查网络')
    }
    return Promise.reject(error)
  }
)

解释:

  • 响应拦截器第一个参数是成功回调,第二个是失败回调。

  • 我们根据 code 统一处理业务成功/失败,组件里只接收成功后的 data,干净很多。

  • 错误状态码(404/500)也在这里统一弹提示,不用在每个请求的地方重复写。


六、封装具体的 API 接口

现在 request 实例已经很强了,但我们在组件里还不想直接写 request.get('/user/login')。最好把接口都集中管理,新建一个 src/api 文件夹,每个模块一个文件。

6.1 用户相关接口 src/api/user.js

javascript

复制代码
// src/api/user.js
// 引入刚才封装好的请求实例
import request from '@/utils/request'

// 登录接口
export function login(data) {
  return request({
    url: '/user/login',    // 接口路径,会自动拼上 baseURL
    method: 'post',        // 请求方法
    data: data             // post 请求体数据
  })
}

// 获取用户信息
export function getUserInfo(userId) {
  return request({
    url: `/user/${userId}`, // restful 风格
    method: 'get'
  })
}

// 更新用户信息
export function updateUser(data) {
  return request({
    url: '/user/update',
    method: 'put',
    data: data
  })
}

// 退出登录
export function logout() {
  return request({
    url: '/user/logout',
    method: 'post'
  })
}

6.2 在组件中使用

vue

复制代码
<template>
  <div>
    <button @click="handleLogin">登录</button>
    <p v-if="user">{{ user.name }}</p>
  </div>
</template>

<script setup>
import { login, getUserInfo } from '@/api/user'
import { ref } from 'vue'

const user = ref(null)

async function handleLogin() {
  try {
    // 调用登录接口,传入用户名密码
    const result = await login({ username: 'admin', password: '123456' })
    console.log('登录成功返回的数据:', result)
    
    // 假设登录后返回了 token 和 userId
    localStorage.setItem('token', result.token)
    
    // 再获取用户信息
    user.value = await getUserInfo(result.userId)
  } catch (error) {
    console.error('登录流程出错:', error)
  }
}
</script>

好处:

  • 组件里看不到任何路径和请求细节,只调用 login() 就行。

  • 以后接口路径变了,只需要改 api/user.js,不用全局搜索改组件。


七、取消重复请求

有时候用户手快点了两下提交按钮,同时发出两个一样的请求,容易造成数据错乱。我们可以在请求拦截器里做请求去重:同一个请求在上一个还没完成时,自动取消上一个。

javascript

复制代码
// src/utils/request.js 完整版(在上面的基础上增加取消请求功能)

import axios from 'axios'

// 用于存放正在进行的请求的标识和取消函数
const pendingMap = new Map()

// 生成请求的唯一标识(url + method + 参数)
function getRequestKey(config) {
  const { url, method, params, data } = config
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}

// 添加请求到 pendingMap
function addPending(config) {
  const key = getRequestKey(config)
  // 如果已经有相同请求在进行,就取消上一个
  if (pendingMap.has(key)) {
    const cancel = pendingMap.get(key)
    cancel('请求被取消,原因是重复请求')
    pendingMap.delete(key)
  }
  // 给当前请求添加 cancelToken
  config.cancelToken = new axios.CancelToken((cancel) => {
    pendingMap.set(key, cancel)
  })
}

// 请求完成后,从 pendingMap 中移除
function removePending(config) {
  const key = getRequestKey(config)
  if (pendingMap.has(key)) {
    pendingMap.delete(key)
  }
}

// 创建实例
const request = axios.create({ baseURL: 'http://localhost:3000/api', timeout: 10000 })

// 请求拦截器
request.interceptors.request.use((config) => {
  // 处理 token(同前面)
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  
  // 取消重复请求
  addPending(config)
  
  return config
}, (error) => Promise.reject(error))

// 响应拦截器
request.interceptors.response.use((response) => {
  // 请求完成,移除 pending
  removePending(response.config)
  
  // 和前面的处理一样
  const res = response.data
  if (res.code === 200) return res.data
  // ... 其他处理
  return Promise.reject(new Error(res.message))
}, (error) => {
  // 如果是取消请求,特殊处理
  if (axios.isCancel(error)) {
    console.log('请求已取消:', error.message)
  } else {
    // 其他错误处理
  }
  // 也要移除 pending
  if (error.config) removePending(error.config)
  return Promise.reject(error)
})

export default request

解释:

  • 用一个 Map 存正在进行的请求和对应的取消函数。

  • 请求前检查是否有相同的 key,有就取消上一个。

  • 请求完成(成功或失败)后移除 key。

  • 组件里不需要任何改动,完全是透明的。


八、环境变量管理

开发环境和生产环境的 API 地址不一样,我们不能每次上线都改代码。Vite 项目支持环境变量文件。

项目根目录新建三个文件:

.env.development

text

复制代码
VITE_API_BASE_URL=http://localhost:3000/api

.env.production

text

复制代码
VITE_API_BASE_URL=https://api.yourdomain.com

然后修改 request.js 中的 baseURL

javascript

复制代码
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,  // 根据环境自动切换
  timeout: 10000
})

import.meta.env.VITE_xxx 就是读取 .env 文件里 VITE_ 开头的变量。开发时自动用 development,打包上线时自动用 production。


九、实战:封装一个完整的请求 Hook

结合前面学的组合式函数,我们可以把"发请求+loading+error"封装成一个 useRequest

javascript

复制代码
// src/hooks/useRequest.js
import { ref } from 'vue'

export function useRequest(apiFn) {
  // apiFn 是一个返回 Promise 的函数(比如 () => login(data))
  
  const data = ref(null)      // 存放响应数据
  const loading = ref(false)  // 是否正在加载
  const error = ref(null)     // 错误信息

  async function execute(...args) {
    loading.value = true
    error.value = null
    data.value = null
    try {
      const result = await apiFn(...args)
      data.value = result
      return result
    } catch (err) {
      error.value = err.message || '请求失败'
      throw err
    } finally {
      loading.value = false
    }
  }

  return { data, loading, error, execute }
}

在组件中使用:

vue

复制代码
<template>
  <div>
    <button @click="doLogin">登录</button>
    <p v-if="loading">登录中...</p>
    <p v-else-if="error">错误:{{ error }}</p>
    <p v-else>用户:{{ data?.name }}</p>
  </div>
</template>

<script setup>
import { useRequest } from '@/hooks/useRequest'
import { login } from '@/api/user'

// 不需要手动管理 loading/error,useRequest 帮你全管了
const { data, loading, error, execute: doLogin } = useRequest(
  () => login({ username: 'admin', password: '123456' })
)
</script>

这么一来,组件里关于请求的代码短到只剩一行调用,干净到令人发指。


十、总结

今天我们完整走了一遍 Axios 在 Vue 项目里的最佳实践:

  1. 安装并创建实例:统一配置 baseURL、超时时间。

  2. 请求拦截器:自动加 token。

  3. 响应拦截器:统一处理返回格式、错误码、弹提示。

  4. 接口统一管理 :按模块放在 api/ 文件夹,组件只调函数。

  5. 取消重复请求:避免手快发两次的问题。

  6. 环境变量:开发、生产自动切换地址。

  7. 封装 useRequest:把 loading/error 逻辑也抽出来,组件极简。

这下你的 Vue 项目跟后端对接就非常丝滑了。把上面的代码按步骤加到你的项目里,以后所有请求都井井有条。

有问题评论区说,我挨个回。下篇见!

相关推荐
良逍Ai出海1 小时前
免费模板搭完独立站后,我用 Codex + Figma 做了自己的页面设计
前端·人工智能·figma
纽格立科技2 小时前
DRM 发射端链路图(下)
前端·人工智能·车载系统·信息与通信·传媒
代码小库2 小时前
【2026前端转 AI 全栈指南】第 2 章(下):NestJS 项目创建 · MongoDB 配置 · 项目启动与调试
前端·数据库·mongodb
之歆2 小时前
Promise 基础技术深度解析:从回调地狱到链式调用
前端·okhttp·promise
甲维斯2 小时前
国产版“Codex”初体验,智谱ZCode很强啊!
前端·人工智能·ai编程
道友可好2 小时前
AI 怎么自己跑完一个 6 小时的任务?
前端·人工智能·后端
To_OC2 小时前
通义千问多模态生图踩坑记:我是如何把两个报错逐个干翻的
前端·aigc·vite
wuxia21182 小时前
在5种环境中编写点击元素改变内容和颜色的JavaScript程序
javascript·微信小程序·vue·jquery·react
Bigfish_coding2 小时前
前端转agent-第一周【python】-02 FastAPI与Pydantic实战(TS/JS视角)
前端