资讯动态

Vue接口统一管理:Axios实例与拦截器封装实战

发布时间:2026/10/17 9:28:56 来源:尧图企业网站定制
1. 接口统一管理的思路拆解先从一团乱麻说起我见过不少Vue项目刚起步两三个页面的时候接口请求代码都写在组件里每个页面自己拉数据、自己处理错误、自己定义URL看起来还挺舒服。但等页面一多、模块一拆分、后端接口路径开始按版本迭代之后组件里的请求代码就会变得非常难看。问题集中在几处同一个接口路径在多个页面重复粘贴后端改了路径或者加了一个通用请求头你得全局搜索替换一个请求失败了有的页面弹个Message有的页面直接不处理最后用户看到的就是一堆奇怪的白屏和未捕获异常。接口统一管理要解决的核心问题不是“少写几行代码”而是让项目中所有的网络请求行为变得可控。什么叫可控就是接口的地址、入参结构、出参结构、错误处理、身份验证方式都只有一个入口。任何人打开项目看一眼api目录就知道这个项目跟后端联调了哪些接口每个接口大概长什么样。这个价值在项目交接、多端复用、接口监控场景下尤其明显。工作几年之后我个人有个判断标准一个项目的接口层能不能称得上“健康”看三件事。第一有没有单独的api目录且按业务模块划分文件第二有没有一个统一的请求实例而不是每个文件裸用axios第三业务请求函数是不是返回Promise并且对返回值做了类型和结构上的约定。三条都满足后面接手的开发者会省很多时间你也能少回答很多没有意义的问题。这篇文章就以Vue项目加axios插件为例把我自己的封装思路、踩坑记录和排查经验完整拆开讲清楚。适合阅读这篇文章的读者是那种已经会写基础组件、会用axios发请求但想把项目里越来越乱的请求代码收敛一下的开发者。不管你是刚开始独立负责一个中型项目还是在旧项目里被喊着“整理一下代码”这套方案都能直接落地。2. api模块的目录划分与职责边界把文件放对位置后面就赢了2.1 一个能直接用五年而不乱的项目结构参考接口统一管理不是把所有请求塞进一个文件那样文件会膨胀到上千行比不整理还要糟糕。正确做法是按业务域拆模块文件每个文件只负责自己那一片的接口。我这里给出一套实际项目里验证过的目录结构不复杂但边界很清楚src/ api/ request.js // 统一封装axios实例和拦截器 index.js // 聚合导出所有接口模块 modules/ auth.js // 登录、注册、刷新token这类接口 user.js // 用户信息、资料修改 order.js // 订单相关接口 product.js // 商品、分类、搜索 common.js // 字典、配置、上传等公共接口 store/ modules/ user.js // pinia或vuex的user模块 utils/ auth.js // token存取的工具函数这套结构的核心原则是request.js里不写具体业务接口modules下的文件不直接创建axios实例index.js只是一个搬运工。各司其职谁也别越界。实际项目中见过一些把“所有接口都堆在一个herlper.js里”的项目那不是在整理是在制造更大的麻烦。modules文件的命名建议跟后端模块对齐跟store里的模块名字段保持一致。比如后端有一个订单中心你前端叫order也算对应反过来store里叫orderStore也可以反正命名越直观越好。刚开始你可能会觉得文件多但一个模块一个文件的好处是以后某个模块废弃了直接删文件就行不会误伤到其他接口。2.2 文件边界划清楚之后接口函数怎么写才不会乱模块文件里面每个接口导出的不是普通函数而是一个“返回Promise的、自带请求配置的函数”。这样做的好处是调用方根本不关心请求是怎么发的只管await这个函数拿到结果后处理业务。我默认的写法是直接在函数里调一个从request.js导出的http实例// api/modules/user.js import http from ../request export function getUserInfo(params) { return http.get(/api/user/info, { params }) } export function updateUserInfo(data) { return http.put(/api/user/info, data) } export function deleteUser(id) { return http.delete(/api/user/${id}) }这里有一个约定路径里的参数不要拼字符串拼到中间去能走params查询参数就走params必须路径传参的用模板字符串并且传参前要检查一下是不是有值。我之前因为顺手拼了一个undefined给后端结果后端把这个undefined当成字符串存进数据库排查了整整一晚上。接口函数的入参、返回结果都应该在注释里写清楚最好写一个范例// 获取用户列表 // 参数: { pageNum, pageSize, keyword } // 返回: { list: [], total: 0 } export function fetchUserList(params) { return http.get(/api/user/list, { params }) }调用方看到注释就知道这个接口怎么用不需要再翻接口文档。时间长了这份注释和代码本身就是文档。2.3 index.js聚合导出的两个理由有的团队喜欢在组件里直接 import 某个模块文件比如import { getUserInfo } from /api/modules/user这没毛病。但保留一个index.js聚合导出从统一入口拿到所有接口体验会更好// api/index.js export * from ./modules/auth export * from ./modules/user export * from ./modules/order export * from ./modules/product export * from ./modules/common组件里的引用就从“知道具体路径”变成“只知道api这个总入口”import { getUserInfo, fetchOrderList } from /api这样带来两个好处第一组件里不会出现零散的深层相对路径重构时移动文件也只需要改index.js第二代码审查的时候reviewer只需要打开index.js就能看到全项目暴露了哪些接口非常方便。当然如果你项目特别小不建index.js也能跑只是我建议从一开始就养成这个习惯。3. axios实例与拦截器的三层封装request.js是整套体系的发动机3.1 为什么不能裸用axios而是必须创建一个实例连接后端服务的axios配置最忌讳的是在业务代码里直接axios.get(/api/xxx)。因为一个个裸用axios你就没有办法统一配置baseURL、超时时间、请求头、取消请求等能力。创建一个独立的axios实例相当于在所有请求前面放了一个总调度室请求从这里被发出去响应也从这里先经过一道检查再回到业务方。// api/request.js import axios from axios const http axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 10000, headers: { Content-Type: application/json;charsetutf-8 } }) export default httpbaseURL这里用的import.meta.env.VITE_API_BASE_URL是Vite项目下的环境变量写法如果你还在用vue-cli(webpack)写项目改成process.env.VUE_APP_API_BASE_URL即可。把环境相关的配置放进环境变量里读取的时候有一个兜底默认值这样的代码无论在开发环境还是生产环境运行都不用改逻辑。不要小看这个“创建一个实例”的操作。实例本身就相当于一个隔离区你可以同时保留一个默认的axios对象给自己的第三方插件SDK用两者不会互相污染。比如项目中引入了某个上传插件它内部可能也会调用axios你不希望它的拦截器被你的全局错误逻辑影响这时候独立实例就起到隔离作用了。3.2 请求拦截器把身份信息和通用参数统一塞进去请求拦截器最常见的用途是自动携带token以及把一些通用参数拼到query上。我见过最粗糙的写法是业务代码自己手动在请求头里拿token、写token一百个接口重复一百遍。拦截器要解决的就是重复这件事情。http.interceptors.request.use( (config) { const token getToken() if (token) { config.headers config.headers || {} config.headers.Authorization Bearer ${token} } return config }, (error) Promise.reject(error) )这里有两个细节值得注意。第一个细节headers这个字段不能直接config.headers.Authorization xxx完事要确保 headers 对象存在。插件或自定义配置可能提前把headers设置成某个非对象值稳妥做法是先判空。第二个细节token的来源不要从组件或store里直接读。正确做法是从一个独立的utils模块拿到token。为什么因为store模块在初始化的时候可能还没有执行或者拦截器里 imports store 会出现循环依赖的问题。从utils里读localStorage是稳定可靠的。按照后端约定如果接口要求不只带token还要带一些固定参数比如版本号、渠道号、项目标识也都可以放到这个拦截器里统一拼接。现在已经有一个很好听的场景叫“水印参数”就是每个请求都带上当前用户ID和页面ID方便后端排查问题这个也是拦截器顺手能做的。3.3 响应拦截器先剥壳再交数据最后统一扭头处理错误响应拦截器是整个request.js里最值得花心思的地方。业务开发中大多数后端返回的结构都是一种“外壳加数据”的格式{ code: 0, message: sucess, data: { list: [], total: 0 } }我相信没人希望在业务代码里每次都写res.data.data.list这种链式取值丑且容易出错。正确的做法是在响应拦截器里就直接判断code成功时只把data返回给业务方失败时直接统一抛错、统一提示。这样业务代码拿到的是一个纯净的数据对象。http.interceptors.response.use( (response) { const res response.data // 二进制数据或其他类型直接return原始response if (response.request.responseType blob || response.request.responseType arraybuffer) { return response } // 后端规范一般定义code为0代表成功具体按项目约定调整 if (res.code 0) { return res.data } // 业务错误统一提示并按约定抛出 ElMessage.error(res.message || 请求失败) if (res.code 401 || res.code 403) { // token失效特殊处理后面详细说明 handleTokenExpired() } return Promise.reject(new Error(res.message || Business Error)) }, (error) { // HTTP层错误4xx、5xx、超时、断网都走这里 const status error.response error.response.status if (status 401) { handleTokenExpired() } else if (status 404) { ElMessage.error(请求资源不存在) } else if (error.code ECONNABORTED) { ElMessage.error(请求超时请稍后重试) } else if (!error.response) { ElMessage.error(网络异常请检查网络连接) } else { ElMessage.error(服务异常(${status})) } return Promise.reject(error) } )这里最关键的理念在于业务错误和HTTP错误是两种不同的错误必须分开处理。业务错误是请求成功返回了但code不对数据其实不可用HTTP错误是请求根本就没成功。很多新手容易把两者混在一起导致一个HTTP 500的错误被当成正常响应去处理data字段结果查半天查不出来。关于handleTokenExpired需要做的事情也不是简单清掉token就完了。你还要考虑并发请求的场景用户在某个页面同时发起了3个请求3个请求都因为token失效返回了401如果你在每个拦截器里都跳一次登录页那用户就会看到3次跳闪体验极差。我这里给出的实践思路是设一个标识位let isRedirecting false function handleTokenExpired() { if (isRedirecting) return isRedirecting true removeToken() router.replace(/login) }同时在store里记录一下“登录态失效”的状态这样下次进入需要登录的页面路由守卫可以直接感知到。这是实际项目里非常容易出现的一个坑我特意单独做了一节说明你就知道它有多容易踩。3.4 响应拦截器返回值的类型约定拦截器是一个中转站返回值会影响所有调用方。我个人的约定是请求成功且业务code为0return res.data此时业务方接收到的就是data对象本身。请求成功但业务code不为0统一提示return Promise.reject(new Error(...))业务方进catch。HTTP层出错统一提示return Promise.reject(error)业务方进catch。文件下载等场景责任写到调用方那里因为后端可能返回二进制流没法走统一code判断所以要特殊放行。这样业务方的代码写起来就很干净try { const listData await fetchUserList({ pageNum: 1 }) // 这里拿到的已经是 listData 了不需要再 res.data.data } catch (e) { // 出错已经被统一处理了这里可以做一些业务上的回退 }3.5 上传和下载场景的拦截器特殊处理接口统一管理最容易被忽略的场景就是文件上传和下载。文件上传时Content-Type要变成multipart/form-data之前实例里的默认配置要允许单个请求覆盖。文件下载时如果后端直接返回文件流响应拦截器里不做code判断且要把整个response包含headers里的content-disposition交给调用方不然你拿不到文件名。上传时的一个实用写法export function uploadFile(file) { const formData new FormData() formData.append(file, file) return http.post(/api/upload, formData, { headers: { Content-Type: multipart/form-data } }) }下载时稍微绕一点export function downloadFile(params) { return http.get(/api/file/download, { params, responseType: blob }) } // 调用方 const blobData await downloadFile({ fileId: xxx }) // 用Blob对象手动创建一个下载链接 const url URL.createObjectURL(blobData) const a document.createElement(a) a.href url a.download fileName a.click() URL.revokeObjectURL(url)下载还有一个小坑如果后端返回的是一个JSON错误对象因为responseType是blob你收到的会是一个乱码Blob。排查的时候可以把Blob再转成文本看看里面是什么内容。这个后面在问题排查一节里再详细聊。4. 接口函数需要定义在api层关于默认参数与路径拼接的那点事接口统一管理通常跟一个更细的约定配合极端情况下我们不建议在组件里自己拼URL或自己设置请求参数。比如排序、分页这些通用参数可以考虑在api层统一兜底。export function fetchOrderList(params {}) { return http.get(/api/order/list, { params: { pageNum: 1, pageSize: 10, ...params } }) }这样做的好处是即使调用方某次忘了传分页参数至少不会把全量数据一次性拉下来把页面打崩。在实际项目中有些后端接口不传pageSize时可能返回全部数据一万条记录直接渲染到表格里页面基本就卡死了。在api层做一个默认参数兜底成本极低但收益很明显。关于路径传参后端接口设计有时候会要求/api/order/{orderId}/items这种格式建议在api层就明确拼接好并且对ID做一次编码处理。尤其当ID来自用户输入或地址栏的时候不做encodeURIComponent直接拼上去万一包含特殊字符后端解不出来或者存在某些安全风险都麻烦。安全方面的处理要认真做。还有一类请求比如“一键取消全部订单”一个操作要调好几个接口或者一个操作需要在某些条件下选择不同接口这些复杂逻辑我也建议放在api层或者对应的业务层而不是让每个组件页面自己写。组件只负责“视图和用户的交互”网络细节这些工作全收敛到外面去。5. 与状态管理的协作接口与store的数据流转边界5.1 接口函数返回的数据应该存到哪里业务开发中一个典型的问题登录之后用户信息是应该直接在组件里const user await getLoginUser()然后用还是放进状态管理里全局共享正常项目里用户信息、权限标识、配置字典这类数据明显需要全局共享应该放进store。而一次性的下拉框数据、列表筛选条件、当前操作的临时状态留在组件内部完全没问题。接口统一管理与状态管理协作的最佳姿势是api层拿数据回来组件或某个action去setStore。不要在接口模块文件里直接调用store的action去修改数据那样接口模块就依赖业务模块了复用性会很差。但有一个例外上一节提到的token失效处理因为要往store里写“登录态失效”标识这属于全局性的会话状态可以在拦截器里用store。这个例外要靠架构约定来约束否则过一段时间谁都会往request.js里塞点业务。还有一个接口数据与store同步的经典场景用户登录后要同时拉取用户信息、权限列表、菜单树、系统配置。这种“多接口并行全部成功才进入主流程”的场景用Promise.all最合适了。接口统一管理之后的优势也在这里你可以很轻松地写一个登录后的初始化逻辑不会因为某个接口失败导致后续流程乱了async function afterLogin() { const [userInfo, permissions, menus, sysConfig] await Promise.all([ getUserInfo(), getPermissionList(), getUserMenus(), getSysConfig() ]) userStore.setUserInfo(userInfo) permStore.setPermissions(permissions) menuStore.setMenus(menus) configStore.setConfig(sysConfig) }如果主流程任意一个接口失败catch里可以做登录后初始化失败的清理。这套模式比在导航守卫里一个个await要清晰得多。5.2 接口模块内部不要出现“风暴式”的循环依赖一个真实项目里经常出现的情况api模块想用store里的数据store模块的action又调用了api模块的接口看起来是同事之间的互相帮助结果就形成了circular dependency。这种问题在打包和运行的时候不一定立刻报错表现为某个接口的request打不出来或者报“Cannot access xxx before initialization”排查起来极其痛苦。我的处理原则是api/request.js这些底层模块只能依赖工具函数utils不依赖store模块。而store模块可以自由地依赖api模块。这是一个单向依赖约束一旦建立起来循环依赖的问题从根上就很少发生。真要读取store数据怎么办比如某些业务接口需要传用户ID组件调用时可以从store里取到后当参数传进来api层不感知store这就是最简单的解耦方案。6. 多环境与多服务端点的支持一个实例不够用怎么办很多项目不会只对接一个后端服务。前端页面里可能有一部分请求走业务后端一部分请求走文件服务还可能有一段请求走第三方开放平台。把不同服务的baseURL都塞进一个axios实例里结果就是baseURL要么固定一个要么在业务代码里手动改URL非常不优雅。我建议的方案是“一主多辅”的结构首先有一个默认的http实例负责大部分业务请求。然后根据实际需求另外创建一两个独立的实例供特殊服务使用。比如文件服务一般是独立部署的可以单独封一个uploadRequest// api/uploadRequest.js import axios from axios const uploadHttp axios.create({ baseURL: import.meta.env.VITE_UPLOAD_BASE_URL, timeout: 60000 // 上传文件超时时间一般更长 }) // 可以不需要业务token或者携带另一种签名但如果你遇到的是一个服务下同时有多个前缀的情况比如/api/user和/api/order都在同一个后端网关下那就没必要分别建实例了。baseURL统一设成域名级别路径前缀留在接口函数里这样改动最小。经验法则多实例用于解决“多个不同服务地址”的问题不用于解决“一个地址下的不同前缀”的问题。环境变量的使用在多服务场景里特别重要。开发环境、测试环境、预发环境、生产环境每套环境的文件服务和业务服务地址都可能不一样。如果你的代码里写死了某个环境的域名到了部署阶段就会出问题。这里建议统一使用环境变量以Vite为例# .env.development VITE_API_BASE_URL/api VITE_UPLOAD_BASE_URL/uploads # .env.production VITE_API_BASE_URLhttps://api.example.com VITE_UPLOAD_BASE_URLhttps://files.example.com这样不同环境打包时自动读取对应的环境变量文件不需要修改任何业务代码。换环境部署的时候只需要确认环境变量配得对不对而不是全局搜索替换域名。还有一种动态需求的场景接口路径不是固定的比如从后端配置中心动态拿到某一个页面的数据URL。这种接口不适合放进静态的api模块里但依然要经过统一实例发请求。可以从后端拿到URL后调http.get(url)动态请求。请求拦截器照样会带上token错误处理照样生效这其实才是统一管理最有价值的地方。7. 请求与响应的统一日志与取消机制两种容易被忽略的隐藏能力7.1 给拦截器加一层“开发环境日志”我见过太多人调试接口的方式先在浏览器控制台手动输一遍请求再复制到代码里跑一遍最后还得打开Network面板看载荷。其实你可以在响应拦截器里加一个开发环境专用的日志输出每次请求的路径、参数、耗时、返回数据全部自动打印省一个手动看网络面板的流程if (import.meta.env.DEV) { console.groupCollapsed([HTTP] ${config.method.toUpperCase()} ${config.url}) console.log(params:, config.params || config.data) console.log(time:, Date.now() - startTime ms) console.log(response data:, res) console.groupEnd() }注意这个日志只需要在开发环境开启生产环境不要打印避免控制台被刷爆也避免数据内容被无关人员看到。安全的做法是完全用环境变量控制是否开启而不是靠代码里的if注释来切换。7.2 页面销毁时怎么取消还在进行的请求切换页面后上一个页面的接口还在飞之后响应回来了不仅可能导致数据渲染到已经销毁的组件上还白白浪费流量。一般有两种处理思路。第一种思路是借助axios的AbortController或CancelToken在组件卸载时取消。Vue组合式API写起来很顺手import { onUnmounted } from vue import { fetchOrderList } from /api let abortCtrl null function loadData() { abortCtrl new AbortController() fetchOrderList({ pageNum: 1 }, { signal: abortCtrl.signal }) } onUnmounted(() { abortCtrl abortCtrl.abort() })当然这要求接口函数能支持透传配置。你可以把api函数写得宽松一点第二个参数专门透传axios的config这样调用方就能自定义signal了export function fetchOrderList(params {}, config {}) { return http.get(/api/order/list, { params, ...config }) }第二种思路是整体方案用一个请求队列自动追踪所有pending请求路由切换时统一取消。这个改造量会大一些但对大型后台系统很值得。我实际做过的一次方案是在请求拦截器里动态给config挂一个标志位记录当前路由标识响应拦截器里把对应的pending请求移除路由切换时遍历队列把同路由下的请求全部abort。核心代码不复杂但收益非常明显。如果你现在的项目页面里大量存在快速切换路由导致接口错乱的情况这个思路可以落地。7.3 避免取消请求之后出现“用户明明没做错什么却看到报错”取消请求后axios会抛出一个CanceledError。如果你在响应拦截器的error分支里把这个错误当成普通异常一样弹出“网络异常请稍后重试”用户会非常困惑。正确做法是识别取消类型并且安静地放行if (axios.isCancel(error)) { return Promise.reject(error) }这样在业务方的catch里如果收到取消错误就不做任何提示。这个细节看着小但是在有自动取消场景的项目里它直接决定了用户体感是“顺滑”还是“出bug了”。8. 常见问题与排查技巧实录这是只有踩过坑才能写出来的部分8.1 问题速查表现象常见原因排查方向所有请求的baseURL都不对404了环境变量文件没生效或命名错误检查 .env 文件命名、变量前缀、是否需要重启dev server请求能发出但后端拿不到token请求拦截器没执行或headers被覆盖检查拦截器是否注册、config.headers是否被提前重置响应结构明明是res.data.xxx却拿到了undefined拦截器里已经return了res.data业务重复拆了一层在响应拦截器里打日志看实际返回值401后重复跳转登录页并发多个请求都返回401加上重定向互斥标志见前文上传文件时Content-Type变成了application/json单个请求没有覆盖实例默认headers上传函数里显式指定 multipart/form-data下载文件打不开或打开是乱码后端返回的是JSON错误但前端按blob处理了把blob转文本检查是不是错误信息接口请求耗时忽长忽短偶尔超时后端慢或代理服务器不稳日志里看具体耗时分布不能只看平均时间打包后发现请求打到开发环境了生产环境变量文件没配或者引用了错误变量在生产环境构建脚本里显式加载对应env文件取消请求后页面还是弹错误提示取消的错误被当成普通错误处理拦截器里加上axios.isCancel判断8.2 两个最值得展开的排查思路第一个是响应拦截器返回后出现两次解构的问题。项目里一旦有人不按约定写在拦截器里已经return res.data了但新来的同事又在业务代码里写成const { list } (await fetchUserList()).data结果就是list永远拿不到而且他还不明白为什么不报错。遇到这种情况我一般直接修改约定业务代码“永远不要使用.data”来取响应结果因为api层已经把data外壳剥掉了。代码评审时可以排一条规则用正则检查业务代码中await xxx()).data的出现。第二个是具有共性的“业务正常但请求报错”的问题。有些团队会把HTTP 200响应正常返回作为唯一判断标准其实后端经常出现这样的情况HTTP层返回200但是业务code非0。如果不在拦截器里统一判code业务层就会走到成功分支然后对出错的数据做渲染页面上只显示一片空或者一堆默认值用户和开发者都看不出来到底发生了什么。排查的时候直接看Network面板的响应体就能迅速定位是code问题还是网络问题。8.3 给调试期的前端开发者三条独家建议第一条数据接口联调时先在浏览器Network面板里确认请求已经发出、响应已经返回再怀疑代码逻辑。很多问题其实卡在代理、跨域或环境变量层面跟代码没关系。你把一个接口从头到尾走一遍就能筛掉一大半可能原因。第二条在响应拦截器成功分支的第一行打印一下response.config.url和response.data会比你在业务组件里console.log一遍有用得多。因为你要看的是链路末尾收到的真实数据而不是某个局部变量。第三条如果你在做自动化测试或者接口比对建议暴露一个不带拦截器的纯axios实例或者导出一个requestWithRawConfig但仅供测试或特殊场景使用。生产业务请求一律走带拦截器的统一实例不要为了调试方便把拦截器给关了。9. 接口版本管理与扩展方向统一管理之后还能做什么接口统一管理不是终点它只是地基。地基打好之后你可以在上面做很多提升开发效率和安全性的东西。最基本的扩展是接口版本管理很多后端在迭代时不会直接改旧接口而是新增一个新版本号路径比如从/api/user/list变成/api/v2/user/list。如果你之前是全局一把梭这种改动会改到怀疑人生。有了模块化的api层只需要在对应模块里新增函数并标注版本旧函数留着兼容老逻辑哪一天旧的不用了直接删掉即可。更进一步你还可以给api层加一个“接口参数校验”的能力。后端字段命名规范比较严格的时候可以在api函数入参处做一次简单的校验比如必传参数没传就直接在控制台警告或抛错避免等请求到后端打出错误日志再返回来浪费时间。这种能力的加法和删除都很轻适合团队里前后端契约还不太稳定的阶段去用。还有一个我现在越来越重视的方向接口文档的自动化生成。如果你的后端接口有规范注释或者团队已经在用统一的接口文档平台前端api模块实际上可以做到半自动同步代码。请求方法、路径、字段类型都可以从接口定义文件生成。你手动维护业务注释但网络层的配置和拦截器逻辑保持统一机器人来生成人来审查这已经是很多中大型团队在走的路线。另外一个很实用的扩展是把“接口埋点”和“性能监控”接到统一实例上。因为所有请求都经过拦截器想统计某个页面加载时调用了哪些接口、耗时多少、成功率多少只需要在拦截器里加一个上报函数将数据发到一个日志平台即可。如果你维护的是中后台系统或者有外部客户在用这些数据能帮你快速判断“是前端问题还是后端问题”以及“是不是某个接口拖慢了整页”。这些扩展方向都依赖一个干净、统一、可控的接口层所以我才反复强调接口统一管理这件事越早做越好。你在项目初期把这套结构搭起来后面的改动都是增量式的等项目膨胀了再回头整理就要承担很大的回归风险。10. 说点实在的这套方案上线几个月后我的体会按照上面这套方案重构了一个项目之后我最明显的感受是接口相关的Bug数量下降了一个量级。过去那种“这个页面为什么没数据”“这个报错是哪个接口出来的”类型的问题现在基本消失了。因为每一条请求都会在日志里留下清晰的记录路径、参数、响应数据一目了然。排查问题不再靠脑补而是靠证据。个人经验是接口统一管理真正难的不是写那些封装代码而是坚持约定。只要约定足够清晰并且代码评审的时候多盯两眼后面接手的人也不会轻易跑偏。我自己隔了一段时间再去翻那个api目录仍然能在五秒钟内想起某个接口是什么用途这种感觉跟面对一锅乱账式的项目是完全不同的。如果你正准备给自己的项目搭这套东西我的建议是不要追求一步到位。先把request.js和拦截器建好再挑一个业务模块比如登录和用户全部迁过去跑通之后再把其余模块逐步迁移。中间留一个新旧并存的缓冲期风险会更小。最后再分享一个小技巧迁移完成后可以全局搜索一下是否还有组件里直接出现axios或http.get(这类写法如果有说明还有漏网之鱼全部清掉才算真正完成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑