尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Axios核心原理与Vue 3实战封装:从应用到原理一次讲透

Axios核心原理与Vue 3实战封装:从应用到原理一次讲透 1. 别急着写代码先搞清楚 Axios 到底解决了什么问题我这些年面试前端几乎每次都会问候选人一个问题“你平时怎么发请求”十个人里有八个会回答“用 Axios”。但你再追问一句“为什么用 Axios它到底帮你做了什么”很多人就卡住了。这篇文章我想把 Axios 从“会用”讲到“用得明白”重点聊聊它在 Vue 3 的 setup 语法下怎么做封装、怎么处理各种坑以及我在实际项目里总结出来的一套相对好用的写法。先给没接触过 Axios 的朋友一个直观感受Axios 是一个基于 Promise 的 HTTP 客户端库可以在浏览器和 Node.js 两端跑。你在页面里要请求后端接口、上传文件、下载文件、处理超时、统一在请求头里塞 token、拦截错误并弹提示……这些活儿Axios 全都帮你包了。你没用它之前写 XMLHttpRequest 要自己处理一堆状态码和事件监听用了它之后大部分琐碎工作都由拦截器和实例配置替你完成了。它适合谁来学我认为有三类人受益最大一是刚入门、还在用fetch或者原生 XHR 写请求、代码里到处是重复配置的新手二是已经在项目里用了 Axios但封装逻辑只是把官网文档抄一遍、想要理解封装背后“为什么这么做”的初中级开发者三是在多个项目间切换想沉淀一套可复用请求层方案的前端负责人。我最早接触 Axios 其实是有点嫌弃的觉得浏览器都有fetch了为什么还要多引一个依赖。但后来在一个需要支持老版本浏览器的后台系统里踩了不少 fetch 的坑才意识到 Axios 真正的价值是“一致性”和“扩展性”。接下来的内容我会从它的核心设计讲起把原理、封装、Vue 3 setup 实战、还有我在真实环境里遇到的问题一次说透。2. Axios 核心机制拆解它凭什么成为请求库的事实标准2.1 从 XHR 到 PromiseAxios 的内核其实是“适配器”很多人误以为 Axios 用了什么高级的黑魔法其实它底层就是封了一层XMLHttpRequest。核心思路用一个词概括适配器。浏览器环境里Axios 默认走 XHRNode.js 环境里它走的是 Node 的http模块。你调用axios.get(/user)内部会经过dispatchRequest然后由适配器去真正地发网络请求。你写的代码不需要关心环境差异——同一套 API浏览器和 Node 都能跑这就是适配器模式最实在的价值。那 Promise 是怎么跟 XHR 结合的简单说Axios 在发请求之前会创建一个 Promise 实例把resolve和reject两个函数保存下来。当 XHR 的onload触发并且状态码符合成功范围时调用resolve当网络错误、超时或者状态码在错误范围时调用reject。这个设计决定了后面所有链式调用、拦截器、并发请求都建立在这个 Promise 基础之上。这套机制对普通开发者的启示是什么你在封装的时候不需要修改 Axios 源码只要理解它的执行顺序就能在正确的环节插入你的业务逻辑。Axios 的执行链路是这样的发起请求 → 执行请求拦截器按添加顺序→ 合并配置 → 发送请求 → 执行响应拦截器按添加顺序逆序→ 返回给调用方。这里有个关键点可能很多人没意识到请求拦截器是后添加的先执行还是先添加的先执行答案是“先添加的先执行”而响应拦截器恰好反过来。这个差异我在实际封装的时候被坑过一次后面在“常见问题”部分我会专门讲。2.2 实例、拦截器、取消机制这三个概念决定封装的上限Axios 的 API 设计很简洁但真正决定封装质量的是三件事自定义实例、拦截器和取消请求机制。自定义实例不是让你炫技用的而是“多套配置隔离”的关键。举个例子你项目里有一个接口是上传文件的上传进度需要单独显示超时时间要设置成 30 秒其他接口超时 10 秒就够了。如果你全用全局的axios对象要么手动覆盖每个请求的配置要么在拦截器里写一堆分支判断。但如果创建了自定义实例你就可以在实例级别预设这些差异代码会干净很多。拦截器是 Axios 最有价值的扩展点。请求拦截器最典型的场景就是统一加 token、统一加签名参数响应拦截器最典型的是统一拆包、统一错误提示。拦截器本身是函数你可以写任意逻辑所以它能承载非常多业务规则。我见过团队用拦截器做了全链路日志上报、接口性能埋点、权限不足自动跳登录页、甚至灰度开关的动态逻辑。也就是说Axios 只是一个请求库但拦截器让它变成了你业务架构的一部分。取消请求用的人比较少但一旦遇到就是救命级功能。它的机制是这样的通过AbortController或者 axios 自带的CancelToken来取消一个进行中的请求。Vue 3 里最常见的场景是组件卸载时把还在途中的请求取消掉避免组件销毁后状态更新导致的内存泄漏和无效报错。我特别提一下CancelToken和AbortController的选型问题。CancelToken是 Axios 自己实现的方案官方文档现在标注它是基于已废弃的cancelable promise提案虽然实际上还能用但新项目我建议直接用AbortController这是浏览器标准的 APIAxios 从某个版本开始也原生支持。// AbortController 用法示例 const controller new AbortController(); axios.get(/api/user, { signal: controller.signal }).catch((error) { if (axios.isCancel(error)) { console.log(请求已取消); } }); // 主动取消 controller.abort();2.3 为什么说 Axios 在处理“错误”上是 fetch 的成熟替代fetch 刚出来的时候呼声很高但实际用下来有几个问题很让人头疼第一fetch 只有在网络错误或者请求被取消的时候才会 rejectHTTP 状态码是 404、500 它也会正常 resolve你还得自己判断res.ok第二默认不带 cookie需要额外配置credentials第三拦截器的概念几乎为零想做统一处理你得自己造轮子。Axios 不同它把“HTTP 状态码是否正常”直接内置在validateStatus里默认是 2xx 状态视为成功其他状态码进入reject。所以你在响应拦截器里能明确区分“请求到达后端但返回业务错误”和“网络根本没通”两种场景。这里我补充一个实战经验真正开发里“错误”是有多种层次的。你的请求能发出、服务器也返回了但业务上失败了——比如登录时密码错误这种属于“业务错误”后端会在 response body 里给一个 code还有一种是真的拿不到数据——断网、DNS 解析失败、接口不存在这是“网络/请求错误”。Axios 的拦截器和错误对象区分机制能让你针对不同层次做不同处理。就拿错误对象里的error.response、error.request、error.message来说它们分别代表了“服务端有响应”“请求发出但没响应”“请求配置阶段就出问题”三种情况理清了这一层你的错误提示才会准确而不是一锅乱炖。3. 从零开始封装 Axios一套能直接抄的实战方案3.1 封装前你想清楚这四件事代码才不会写废我见过很多人一上来就写代码封装到一半发现不知道怎么处理 token、不知道统一错误提示放哪。其实封装 Axios 前先花十分钟想清楚下面四个问题后面会顺畅很多。第一你的项目需要几个实例如果一个就够就用默认导出如果业务里既有用户端接口又有开放接口、还有上传接口且配置差异大建议拆成两到三个实例。但注意别拆太多每多一个实例维护成本就多一分。第二token 过期后怎么处理常见的方案有三种请求前检查 token 是否存在不存在就跳登录页请求后收到 401 状态码做统一处理用响应拦截器配合重试机制刷新 token 后再发原来失败的请求。第三种体验最好但复杂度也最高我建议中小项目先做前两种。第三错误提示怎么统一你可以选择在响应拦截器里弹全局提示也可以在调用方 catch 里弹。前者省事但不够灵活有些错误你不想让用户看到后者灵活但容易漏。我的做法是响应拦截器里处理网络层错误和通用业务错误调用方只关心自己特有的业务逻辑。第四返回数据怎么解包大多数后端返回结构是{ code: 0, data: {...}, message: 成功 }你可以选择在响应拦截器里直接返回response.data.data调用方拿到的是什么是一个纯粹的“业务数据”。如果后端结构调整了你只需要改拦截器一处。这个设计让所有接口函数变得异常简洁。3.2 完整封装代码结构、注释、参数一次到位我下面贴的这套封装是我目前在多个后台项目中复用的一套模板基于 Axios 较新的版本TypeScript 和 JavaScript 项目都能参考。先讲目录结构src/ utils/ http/ index.ts // 封装主入口导出 request 实例 request.ts // 创建实例、拦截器逻辑 handleError.ts // 错误分类与提示 type.ts // 类型定义 api/ user.ts // 业务接口模块调用 request 实例为什么要拆这么细因为“请求层”和“业务接口层”应该分开。请求层只关心“怎么发请求”业务接口层只关心“调哪个 URL 传什么参数”。这样当后端换了地址、改了返回结构你只需要改请求层业务代码不用动。// utils/http/request.ts import axios from axios; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from axios; import { ElMessage } from element-plus; import { handleError } from ./handleError; const service: AxiosInstance axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 10000, headers: { Content-Type: application/json;charsetutf-8 } }); // 请求拦截器 service.interceptors.request.use( (config) { // 1. 从 pinia 或 localStorage 取 token const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } // 2. 可以在这里加接口埋点、请求日志 return config; }, (error) { // 请求配置阶段出错直接 reject return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse) { const res response.data; // 文件流直接返回 if (res instanceof Blob) { return res; } // 后端约定 code 0 表示成功 if (res.code 0) { return res.data; } // 业务错误统一提示 ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message || 请求失败)); }, (error) { // 处理 HTTP 状态码错误、网络错误、超时、取消请求等 return handleError(error); } ); export default service;再来看错误处理模块// utils/http/handleError.ts import { ElMessage } from element-plus; import axios from axios; export function handleError(error: any) { // 1. 请求取消属于正常流程不提示 if (axios.isCancel(error)) { console.log(请求已取消:, error.message); return Promise.reject(error); } // 2. 有 response说明服务端返回了错误状态码 if (error.response) { const { status } error.response; switch (status) { case 400: ElMessage.error(请求参数错误); break; case 401: ElMessage.error(登录状态已过期请重新登录); // 清 token、跳登录页等操作 localStorage.removeItem(token); window.location.href /login; break; case 403: ElMessage.error(没有权限访问该资源); break; case 404: ElMessage.error(请求的资源不存在); break; case 500: ElMessage.error(服务器内部错误); break; case 502: ElMessage.error(网关错误); break; case 503: ElMessage.error(服务暂时不可用); break; default: ElMessage.error(请求错误(${status})); } } else if (error.request) { // 3. 请求已发出但没有收到响应 ElMessage.error(网络异常请检查网络连接); } else { // 4. 请求配置阶段出错 ElMessage.error(error.message || 请求发送失败); } return Promise.reject(error); }注意一个细节handleError里最后一定要Promise.reject(error)这样调用方在.catch里还能拿到完整的错误信息做补充处理。如果你把错误吞掉调用方会误以为请求成功了。3.3 为什么响应拦截器推荐返回“业务数据”而不是整个 response这是封装里争议最大的一个点。我的意见很明确在大多数后端接口结构统一的业务项目里响应拦截器直接把response.data.data返回给调用方是最优解。理由是调用方根本不需要关心 HTTP 状态码、响应头、完整 response body 这些细节它只需要“数据”或者“明确的错误”。这么写的好处是业务代码非常干净// 业务接口模块 import request from /utils/http; export function getUserInfo(userId: string) { return request.get(/user/${userId}); } // 调用方 const data await getUserInfo(123); console.log(data.name); // 直接拿到业务数据但也有例外。如果某些接口需要访问响应头里的自定义字段比如分页信息放在 header 里或者你下载文件时需要拿到整个 Blob 对象那就要特殊处理。我的处理方式是默认解包但在拦截器里加一个判断如果返回的数据是 Blob就不解包直接返回。刚才代码里已经体现了这一点。还有一个更细的设计如果你需要“部分接口不解包”可以在 axios 的配置里加一个自定义字段比如config.raw true然后在响应拦截器里判断这个字段决定是返回原始响应还是解包后数据。这种“约定优于配置”的思路能让你的封装适应各种边界情况。3.4 上传、下载、取消、超时几个高频需求的配置细节再分享几个我实际项目里经常用到的配置细节这些写进封装里可以省不少事。上传文件上传接口最核心的诉求是进度条和超时时间。进度条需要监听onUploadProgress它返回的progressEvent.loaded / progressEvent.total就是当前百分比。要注意的是上传接口一般不需要设置太短的超时时间大文件可能跑几分钟10 秒超时会让用户崩溃。export function uploadFile(file: File, onProgress?: (percent: number) void) { const formData new FormData(); formData.append(file, file); return request.post(/upload, formData, { timeout: 60000, headers: { Content-Type: multipart/form-data }, onUploadProgress: (progressEvent) { if (onProgress progressEvent.total) { const percent Math.round((progressEvent.loaded * 100) / progressEvent.total); onProgress(percent); } } }); }下载文件如果是文件流要注意设置responseType: blob否则前端拿到的是乱码文本。拿到 Blob 之后通过URL.createObjectURL生成临时链接并触发一个a标签的点击事件来下载。这个场景我没封装进通用拦截器而是在下载接口函数里单独处理因为它的返回类型跟普通接口不一样。超时设置timeout是毫秒值在实例里设默认值单个请求可以覆盖。但要注意这里的时间是从“请求发出”到“接收完响应头”的时间不是整个响应体下载完成的时间。如果你下载一个大文件Axios 的 timeout 会在响应体还没传完时就触发中断。遇到这种场景建议不要依赖 timeout 做兜底而是用onDownloadProgress自己维护一个“长时间无进度”的超时判断。4. Vue 3 setup 组合式 API 实践从“能用”到“好用”4.1 setup 里直接用 axios 的常见姿势和它的问题Vue 3 引入了 setup 语法后很多新手最困惑的就是“我该在哪里发请求”。直接照着 Vue 2 的思路写一般是这样的import { ref, onMounted } from vue; import axios from axios; const list ref([]); onMounted(async () { const res await axios.get(/api/list); list.value res.data; });这段代码能用但有两个问题第一URL 和配置散落在各个组件里项目一大就失控第二token、错误处理、loading 这些逻辑都是重复的每个组件都要写一遍。所以我的建议是业务组件里永远不要直接出现axios你应该先封装好请求层然后针对业务场景封装 hook。4.2 把 axios 封装成 useRequest一个管理 loading、数据、错误的组合式函数在 Vue 3 setup 里用 axios 的正确姿势之一是把请求逻辑封装成一个可复用的 hook。这里我提供一个useRequest的示例它封装了 loading、错误、数据这几个最常见的状态// composables/useRequest.ts import { ref, type Ref, type UnwrapRef } from vue; type RequestFnT, P extends any[] (...args: P) PromiseT; export function useRequestT, P extends any[] any[](requestFn: RequestFnT, P) { const data refT | null(null) as RefUnwrapRefT | null; const loading ref(false); const error refError | null(null); const run async (...args: P) { loading.value true; error.value null; try { const result await requestFn(...args); data.value result; return result; } catch (err) { error.value err as Error; throw err; } finally { loading.value false; } }; return { data, loading, error, run }; }使用方法// api/user.ts import request from /utils/http; export const getUserInfo (userId: string) { return request.get(/user/${userId}); }; // views/user.vue import { useRequest } from /composables/useRequest; import { getUserInfo } from /api/user; const { data: userInfo, loading, error, run } useRequest(getUserInfo); onMounted(() { run(123); });这个 hook 的精髓在于它把“请求数据”和“页面状态”解耦了。测试的时候你甚至不需要渲染组件直接调用run就能验证请求逻辑。loading 状态是自动更新的你不再需要手动loading.value true再car了。4.3 组件卸载时自动取消请求避免 setState on unmounted 的经典警告Vue 3 组件在销毁后如果请求回调才回来虽然 Vue 3 不像 React 那样会打印警告但依然可能出现“对已销毁组件的响应式状态赋值”导致的内存泄漏风险。怎么优雅地处理结合 AbortController我写了一个带取消能力的 hook 版本// composables/useRequest.ts 增强版 import { ref, onBeforeUnmount } from vue; import type { Ref, UnwrapRef } from vue; type RequestFnT, P extends any[] (...args: P) PromiseT; export function useRequestT, P extends any[] any[](requestFn: RequestFnT, P) { const data refT | null(null) as RefUnwrapRefT | null; const loading ref(false); const error refError | null(null); const controllers new SetAbortController(); const run async (...args: P) { const controller new AbortController(); controllers.add(controller); loading.value true; error.value null; try { const result await requestFn(...args, { signal: controller.signal }); data.value result; return result; } catch (err) { error.value err as Error; throw err; } finally { loading.value false; controllers.delete(controller); } }; const cancel () { controllers.forEach((controller) controller.abort()); controllers.clear(); }; onBeforeUnmount(() { cancel(); }); return { data, loading, error, run, cancel }; }这里用了一个 Set 来管理多个进行中的请求因为同一页面可能同时发多个请求。组件销毁时统一 abort这些请求的 Promise 会进入 reject 分支并且因为我们封装过的handleError里专门识别了取消场景所以不会弹出错误提示。这个细节很重要——你不想让用户看到“网络错误”的提示因为请求取消是用户主动导航导致的不是错误。不过这里有个配合问题你需要让业务接口函数支持传入signal。比如getUserInfo(userId, config)里的config要能被 axios 正常合并。这就是为什么我前期强调要规范你的接口函数签名——每个接口函数最好都留一个可选的 config 参数以便接口层能接管高级用法。4.4 script setup 语法糖下的最终推荐写法如果你用的是script setup整个页面可以写成这样script setup langts import { useRequest } from /composables/useRequest; import { getUserInfo } from /api/user; const { data: userInfo, loading, run } useRequest(getUserInfo); // 页面加载时自动请求 run(123); /script template div v-ifloading加载中.../div div v-else-ifuserInfo p{{ userInfo.name }}/p p{{ userInfo.email }}/p /div /template这段代码非常直观数据、loading、请求方法全是响应式的模板里直接用。没有onMounted的嵌套没有多余的 try/catch页面加载即请求。如果你需要在某些操作后再请求直接调用run即可。如果你有“请求依赖另外一个请求的返回值”这种场景建议不要嵌套而是用watch监听源数据变化时再触发run。这是因为嵌套会导致 loading 状态混乱也不利于代码复用。5. 常见问题速查与实操心得5.1 一张表解决你 90% 的 Axios 报错困惑我整理了一张速查表这些是我在项目里和团队群里遇到频率最高的问题每一个都是我亲自踩过或者帮别人定位过的现象可能原因解决方案请求一直 pending 直到超时跨域请求未配置 CORS或者接口地址写错先看 network 面板排查是不是预检请求失败控制台报Network Error跨域、断网、证书问题本地开发配代理或启用 CORS检查后端是否配置了允许跨域拿到的数据是字符串而不是对象后端返回的不是 JSON 而是纯文本检查后端响应头 Content-Type 是否为 application/json上传文件时进度条不动onUploadProgress没有正确配置确认请求实例允许自定义配置且后端支持上传进度回调多个请求同时触发时拦截器不生效拦截器注册顺序或实例使用错误确认用的是同一个自定义实例而不是全局 axios组件卸载后仍弹出错误提示请求没有在卸载时取消使用 AbortController并在 onBeforeUnmount 里统一取消第一次请求正常第二次一直 401token 过期或刷新逻辑问题检查 token 存储方式和过期时间是否有并发刷新 token 的处理下载文件时中文文件名乱码响应头里的 filename 编码问题使用decodeURIComponent解码或让后端用 RFC 5987 格式返回请求被取消但控制台仍有红色报错没有用axios.isCancel过滤在错误处理里判断 isCancel主动忽略取消场景请求重复发送导致数据错乱组件内多个并发请求未做竞态处理用 AbortController 取消旧请求或用请求序号判断最新响应5.2 请求/响应拦截器执行顺序的一个坑很多人都会栽前面我提到拦截器的执行顺序问题这里展开讲。Axios 拦截器的内部实现是它有一个interceptors数组请求拦截器在请求发出前按“先进先出”顺序执行响应拦截器在数据返回后按“先进后出”顺序执行。这是什么意思如果你注册了 A、B 两个请求拦截器那么 config 会先经过 A 再经过 B但如果你注册了 C、D 两个响应拦截器响应会先经过 D 再经过 C。这个设计是为了保证数据流的方向和直觉一致——请求层层包装响应层层解包。我第一次在这个上面翻车是因为在响应拦截器里加了接口耗时统计结果发现“第一个注册的拦截器里统计到的耗时包含了后面所有拦截器的耗时”如果要统计实际网络耗时应该在最后一个注册的响应拦截器里算。如果不理解这个顺序你写出来的统计就是一团浆糊。5.3 token 刷新与请求重试的经典实现接口返回 401 时比较体面的做法是自动刷新 token然后重放原请求。但这里有两个难点一是刷新 token 的接口本身不能用“带老 token”的实例发否则会死循环二是多个请求同时失败时不能同时发多个刷新请求否则后端会被刷爆。我贴一个简化但可运行的思路// utils/http/refresh.ts let isRefreshing false; let pendingQueue: Array(token: string) void []; async function refreshToken() { // 用 axios.create 一份不携带业务拦截器的实例来发刷新请求 const rawAxios axios.create({ baseURL: /api }); const res await rawAxios.post(/refresh, { refreshToken: getRefreshToken() }); return res.data.token; } export function handle401(error: any) { const config error.config; if (!config._retry) { config._retry true; if (isRefreshing) { // 等待刷新中的 promise 完成再重放原请求 return new Promise((resolve) { pendingQueue.push((token) { config.headers.Authorization Bearer ${token}; resolve(service(config)); }); }); } isRefreshing true; return refreshToken() .then((token) { setToken(token); pendingQueue.forEach((cb) cb(token)); pendingQueue []; config.headers.Authorization Bearer ${token}; return service(config); }) .finally(() { isRefreshing false; }); } return Promise.reject(error); }这段代码的核心技巧是_retry标记和pendingQueue队列。通过标记防止同一请求重复刷新 token通过队列让所有并发失败的请求在 token 刷新完成后统一重放。这个方案我强烈建议你在项目里自己做一遍而不是直接复制因为触发条件和存储方式跟你的认证体系强相关。5.4 我踩过的一些坑提前帮你避开最后分享几个比较零碎但很实用的避坑清单。第一个坑axios.defaults.headers和实例 headers 的合并策略。全局默认配置会被实例配置覆盖但对象层面的合并是深合并还是浅合并Axios 的mergeConfig对 headers 做的是深合并也就是说你实例里设置的 headers 会覆盖全局的同名 header但不会清掉全局里没被覆盖的 header。这个行为在多数情况下是符合直觉的但如果你在某个请求里把Content-Type设为undefined它不会移除该 header而会把它变成字符串undefined传给后端极易导致后端解析 body 失败。处理方式是显式设置为空对象headers: { Content-Type: }但最好还是统一维护一套 header 规范别在单个请求里随意覆盖。第二个坑响应拦截器里不要直接返回response.data就完事要考虑文件流。如果后端返回的是 Blob 类型response.data会被 Axios 自动解析成 Blob但如果你在拦截器里默认就解包成业务数据那下载文件时你拿到的就可能是一堆乱码、甚至直接把 Blob 转成了对象。我的方案前面也说了拦截器里判断res instanceof Blob直接返回。第三个坑并发请求与竞态处理。在搜索框中输入关键字请求接口用户快速输入多个关键字时你无法保证这些请求按顺序返回后发出的可能先返回导致页面展示旧数据。最简单的处理方式就是每次发起新请求前取消上一次请求。如果你用的是AbortController可以在每次请求前controller.abort()并创建一个新的 controller。封装进useRequest后这个逻辑几乎是自动的只需要在run里加一个“先取消之前所有未完成的请求”的动作。第四个坑本地开发环境跨域别急着让后端开 CORS。很多新手遇到Network Error第一反应是后端不肯开跨域。其实本地开发用 Vite 的 proxy 最方便配置很简单// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } });这样你的baseURL写成/api即可浏览器视角是同源的完全没有跨域问题后端也不需要特殊处理。生产环境再把/api通过 Nginx 反代到真实服务地址。这套流程在我们团队已经跑了很多项目稳定性很不错。我个人在实际开发中还有一个习惯把baseURL、超时时间、是否开启 mock 这些配置全丢到环境变量文件里不同环境用不同的.env文件。项目从来不会因为测试环境和生产环境接口地址不一致而出问题。这也是封装的价值——它不只是一个工具函数更是项目工程化的一部分。希望这篇内容能让你把 Axios 用得更明白少走一些我走过的弯路。
返回列表