当前位置: 首页 > news >正文

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 项目跟后端对接就非常丝滑了。把上面的代码按步骤加到你的项目里,以后所有请求都井井有条。

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

http://www.jsqmd.com/news/1013446/

相关文章:

  • Obsidian Dataview完整指南:3步将笔记库变为智能数据库
  • 终极指南:如何让10美元鼠标在macOS上超越苹果触控板体验
  • MPC8245信号与时钟系统解析:SDRAM、I2C、UART及调试接口设计实践
  • APK-Installer:在Windows上安装安卓应用的终极完整指南
  • Dism++:专业Windows系统维护与优化解决方案
  • 爱回收报价透明吗?用三个标准拆开回收定价 - 新闻快传
  • 广州沙发翻新靠谱商家沙发换皮换布 - 我叫一
  • 从Docker到Systemd:在Ubuntu 22.04上部署Jenkins的两种姿势及选型指南
  • Vue3 异步数据管理:从满地都是 loading 到优雅的 useRequest,保姆级优化之路
  • 《鸿蒙原生应用开发实战》第二篇:ArkTS 数据模型与状态管理
  • Notepad--:跨平台文本编辑器的国产之光,打造高效开发新体验
  • SillyTavern终极性能优化实战:从卡顿到流畅的完整指南
  • 5分钟从零制作专业视频:Auto-Video-Generator完全指南
  • (GR-RL)技术密档701-1000号摘要: 本技术文档集聚焦工业级具身智能系统的底层参数与核心算法,涵盖硬件控制、传感融合、运动规划及分布式训练等关键技术指标。主要内容包括:总线仲裁采用伺服驱动优
  • 2026年昆山家电维修机构TOP5盘点 全维度实测对比 - 互联网科技品牌测评
  • 爱回收报价透明吗?三类闲置实测后的判断 - 新闻快传
  • Bugku CTF 神秘的文件
  • MPC7450 MPX总线地址传输机制与缓存一致性实战解析
  • Hitboxer终极指南:免费开源的SOCD键盘重映射工具,彻底解决游戏方向键冲突
  • LaTeX参考文献样式选哪个?8种bibliographystyle(plain/ieeetr/acm...)的详细对比与选择指南
  • 喜报!itc保伦股份荣获第十一届广东专利优秀奖,创新成果再获权威认可 - 品牌速递
  • 国产跨平台文本编辑器终极指南:notepad--如何成为你的高效编程伙伴
  • 爱回收质检透明吗?拆完5道工序我有了判断 - 新闻快传
  • LiteDB.Studio:嵌入式NoSQL数据库的终极可视化管理方案
  • Python量化交易终极指南:Backtrader快速入门与实战教程
  • Ryujinx Switch模拟器完整教程:从零开始快速搭建高性能游戏环境
  • Ryujinx Switch模拟器终极指南:在PC上畅玩任天堂游戏的完整教程
  • 2026年昆山家电故障维修服务商推荐 附选型标准与避坑要点 - 互联网科技品牌测评
  • 杭州闲置黄金怎么卖不亏?2026黄金回收完整避坑攻略,正规门店这样选 - 薛定谔的梨花猫
  • 别再傻傻用ManualResetEvent了!C#高并发场景下,试试这个性能更强的轻量级替代品