深入明白 Axios 三层封装:从零开始构建高效的 HTTP 哀求库 ...

一给  金牌会员 | 2024-11-17 03:08:33 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 749|帖子 749|积分 2247

媒介

在当代前端开发中,Axios 已成为与后端举行数据交互的首选 HTTP 客户端。它基于 Promise,支持浏览器和 Node.js,拥有丰富的功能,如拦截哀求和响应、取消哀求、自动转换 JSON 等。然而,直接使用 Axios 可能会导致代码重复、难以维护。为相识决这些题目,三层封装 的模式应运而生。本文将领导你从零开始,逐步构建一个结构清楚、易于维护的 Axios 三层封装方案,让你在项目中轻松管理 HTTP 哀求。
目录


  • 为什么要封装 Axios?
  • 三层封装架构概述
  • 第一层:底子设置(Base Layer)

    • 安装 Axios
    • 创建 Axios 实例
    • 设置哀求拦截器
    • 设置响应拦截器

  • 第二层:API 接口管理(API Layer)

    • 统一管理 API 接口
    • 模块化 API 接口

  • 第三层:服务调用(Service Layer)

    • 在组件中调用 API
    • 处理响应数据

  • 高级优化与扩展

    • 环境切换
    • 错误处理与提示
    • 取消哀求
    • 断网处理

  • 完备代码示例
  • 总结

为什么要封装 Axios?

固然 Axios 功能强大,但直接在项目中使用可能存在以下题目:


  • 代码重复:每个哀求都需要重复设置,如底子 URL、哀求头等。
  • 维护困难:当需要修改设置时,必须逐一修改每个哀求。
  • 缺乏统一管理:难以集中处理错误、拦截器等逻辑。
通过封装 Axios,可以:


  • 简化哀求:统一设置,淘汰重复代码。
  • 集中管理:统一处理拦截器、错误等,提拔代码可维护性。
  • 模块化:按业务模块管理 API 接口,结构清楚。

三层封装架构概述

三层封装 通常包罗:

  • 底子层(Base Layer):设置 Axios 实例,包罗拦截器、底子 URL 等。
  • API 层(API Layer):定义各个业务模块的 API 接口。
  • 服务层(Service Layer):在业务逻辑中调用 API 接口,处理响应数据。
这种结构使得代码逻辑清楚,易于维护和扩展。

第一层:底子设置(Base Layer)

底子层主要负责设置 Axios 实例,包罗底子 URL、哀求和响应拦截器等。
安装 Axios

起首,通过 npm 安装 Axios:
  1. [/code] npm install axios
  2. [size=3]创建 Axios 实例[/size]
  3. 在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
  4. [code]
复制代码
// src/utils/request.js import axios from 'axios'; import store from '@/store'; // 引入 Vuex 存储 import router from '@/router'; // 引入路由 import { Toast } from 'vant'; // 引入 UI 提示库 // 创建 Axios 实例 const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, // 底子 URL,根据环境变量设置 timeout: 5000, // 哀求超时时间 headers: { 'Content-Type': 'application/json;charset=UTF-8', }, });
设置哀求拦截器

哀求拦截器用于在哀求发送前做一些处理,如添加认证 token。
  1. [/code] // 哀求拦截器 instance.interceptors.request.use( config => { // 从 Vuex 获取 token const token = store.state.token; if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } );
  2. [size=3]设置响应拦截器[/size]
  3. 响应拦截器用于统一处理响应数据和错误。
  4. [code]
复制代码
// 响应拦截器 instance.interceptors.response.use( response => { const res = response.data; if (res.code === 200 || res.code === 0) { return res; } else if (res.code === 401) { Toast.fail('登录状态已过期,请重新登录'); store.commit('SET_TOKEN', ''); router.push('/login'); return Promise.reject(new Error('登录状态已过期')); } else { Toast.fail(res.message || '哀求失败'); return Promise.reject(new Error(res.message || '哀求失败')); } }, error => { if (error.response) { const status = error.response.status; switch (status) { case 401: Toast.fail('未授权,请重新登录'); store.commit('SET_TOKEN', ''); router.push('/login'); break; case 403: Toast.fail('拒绝访问'); break; case 404: Toast.fail('哀求资源不存在'); break; case 500: Toast.fail('服务器内部错误'); break; default: Toast.fail(error.response.data.message || '未知错误'); } } else { Toast.fail('网络错误'); } return Promise.reject(error); } ); export default instance;

第二层:API 接口管理(API Layer)

API 层负责定义各个业务模块的接口,便于集中管理和维护。
统一管理 API 接口

在 src 目录下创建 api 文件夹,并在此中创建 index.js 作为 API 的统一出口。
  1. [/code] // src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
  2. [size=3]模块化 API 接口[/size]
  3. 以用户模块为例,创建 user.js 文件定义相干 API 接口。
  4. [code]
复制代码
// src/api/user.js import request from '@/utils/request'; import QS from 'qs'; // 根据需求是否引入 qs 模块 // 获取用户信息 export const getUserInfo = () => { return request.get('/user/info'); }; // 更新用户信息 export const updateUserInfo = (data) => { return request.post('/user/update', QS.stringify(data)); }; // 用户登录 export const login = (credentials) => { return request.post('/user/login', QS.stringify(credentials)); }; // 用户注册 export const register = (data) => { return request.post('/user/register', QS.stringify(data)); };
同样地,可以为产品模块、订单模块等创建对应的 API 接口文件。

第三层:服务调用(Service Layer)

服务层负责在业务逻辑中调用 API 接口,并处理响应数据。
在组件中调用 API

以 Vue 组件为例,展示如何调用封装好的 API。
  1. [/code] <!-- src/views/UserProfile.vue --> <template> <div> <h1>用户信息</h1> <p v-if="error">{{ error }}</p> <div v-else> <p>用户名: {{ userInfo.name }}</p> <p>邮箱: {{ userInfo.email }}</p> <button @click="saveUserInfo">保存</button> </div> </div> </template> <script> import { getUserInfo, updateUserInfo } from '@/api/user'; import { Toast } from 'vant'; export default { data() { return { userInfo: {}, error: '', }; }, created() { this.fetchUserInfo(); }, methods: { async fetchUserInfo() { try { const response = await getUserInfo(); this.userInfo = response.data; } catch (err) { this.error = '无法获取用户信息'; console.error(err); } }, async saveUserInfo() { try { const response = await updateUserInfo(this.userInfo); Toast.success('用户信息更新成功'); } catch (err) { Toast.fail('更新失败'); console.error(err); } }, }, }; </script>
  2. [size=3]处理响应数据[/size]
  3. 在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
  4. [code]
复制代码
// src/services/userService.js import { getUserInfo, updateUserInfo } from '@/api/user'; export const fetchUserProfile = async () => { try { const response = await getUserInfo(); // 可以在这里举行数据格式化 return response.data; } catch (error) { throw error; } }; export const saveUserProfile = async (profileData) => { try { const response = await updateUserInfo(profileData); return response; } catch (error) { throw error; } };

高级优化与扩展

在底子三层封装的底子上,还可以举行以下优化和扩展,进步代码的健壮性和可维护性。
环境切换

通过环境变量管理不同环境的底子 URL,确保开发、测试和生产环境的设置正确。
  1. [/code] // .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
  2. 在 request.js 中使用环境变量:
  3. [code]
复制代码
// src/utils/request.js const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 5000, headers: { 'Content-Type': 'application/json;charset=UTF-8', }, });
错误处理与提示

统一处理错误信息,并通过 UI 组件库(如 Vant)举行提示,提拔用户体验。
  1. [/code] // 响应拦截器中的错误处理部分 if (error.response) { const status = error.response.status; switch (status) { case 401: Toast.fail('未授权,请重新登录'); // 重定向到登录页 store.commit('SET_TOKEN', ''); router.push('/login'); break; case 403: Toast.fail('拒绝访问'); break; case 404: Toast.fail('哀求资源不存在'); break; case 500: Toast.fail('服务器内部错误'); break; default: Toast.fail(error.response.data.message || '未知错误'); } } else { Toast.fail('网络错误'); }
  2. [size=3]取消哀求[/size]
  3. 从 Axios v0.22.0 开始,推荐使用 [b]AbortController[/b] 来取消哀求,旧的 [b]CancelToken[/b] API 被弃用。以下是两种方法的示例:
  4. [size=2]使用 AbortController[/size]
  5. [code]
复制代码
const controller = new AbortController(); axios.get('/foo/bar', { signal: controller.signal }).then(function(response) { // 处理响应 }).catch(function(error) { if (axios.isCancel(error)) { console.log('哀求被取消', error.message); } else { // 处理错误 } }); // 取消哀求 controller.abort();
使用 CancelToken(不推荐)

  1. [/code] let source = axios.CancelToken.source(); axios.get('/users/12345', { cancelToken: source.token }).then(res => { console.log(res); }).catch(err => { if (axios.isCancel(err)) { console.log('哀求被取消', err.message); } else { // 处理错误 } }); // 取消哀求,参数可选,该参数信息会发送到哀求的catch中 source.cancel('取消后的信息');
  2. [size=3]断网处理[/size]
  3. 通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
  4. [code]
复制代码
// src/main.js window.addEventListener('offline', () => { Toast.fail('网络连接已断开,请检查网络'); store.commit('SET_NETWORK_STATUS', false); }); window.addEventListener('online', () => { Toast.success('网络连接已规复'); store.commit('SET_NETWORK_STATUS', true); });
在 Vue 组件中,根据网络状态显示提示信息。
  1. [/code] <!-- src/App.vue --> <template> <div id="app"> <div v-if="!network" class="offline-banner"> <p>您当前处于离线状态</p> <button @click="onRefresh">重新加载</button> </div> <router-view /> </div> </template> <script> import { mapState } from 'vuex'; import { Toast } from 'vant'; export default { computed: { ...mapState(['network']), }, methods: { // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的 onRefresh() { this.$router.replace('/refresh'); }, }, }; </script> <style> .offline-banner { position: fixed; top: 0; width: 100%; background-color: #ff4d4f; color: white; text-align: center; padding: 10px 0; z-index: 9999; } </style>
  2. 创建 refresh.vue 页面,用于刷新当前页面。
  3. [code]
复制代码
<!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
取消拦截器

偶然候需要在特定环境下移除拦截器,可以通过 eject 方法实现。
  1. [/code] const myInterceptor = axios.interceptors.request.use(function (config) { // 拦截逻辑 return config; }); // 取消拦截器 axios.interceptors.request.eject(myInterceptor);
  2. [hr] [size=4]完备代码示例[/size]
  3. 为了更好地明白,下面提供一个完备的三层封装代码示例。
  4. [size=3]1. 项目结构[/size]
  5. [code]src
  6. ├── api
  7. │   ├── index.js
  8. │   └── user.js
  9. ├── components
  10. ├── router
  11. ├── store
  12. │   └── index.js
  13. ├── utils
  14. │   └── request.js
  15. ├── views
  16. │   ├── UserProfile.vue
  17. │   └── Refresh.vue
  18. └── App.vue
复制代码
2. request.js

  1. [/code] // src/utils/request.js import axios from 'axios'; import store from '@/store'; import router from '@/router'; import { Toast } from 'vant'; /** * 提示函数 * 禁止点击蒙层、显示一秒后关闭 */ const tip = msg => { Toast({ message: msg, duration: 1000, forbidClick: true }); }; /** * 跳转登录页 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面 */ const toLogin = () => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); }; /** * 哀求失败后的错误统一处理 * @param {Number} status 哀求失败的状态码 */ const errorHandle = (status, other) => { // 状态码判定 switch (status) { // 401: 未登录状态,跳转登录页 case 401: toLogin(); break; // 403 token过期 // 打扫token并跳转登录页 case 403: tip('登录过期,请重新登录'); localStorage.removeItem('token'); store.commit('SET_TOKEN', ''); setTimeout(() => { toLogin(); }, 1000); break; // 404哀求不存在 case 404: tip('哀求的资源不存在'); break; default: console.log(other); } }; // 创建 Axios 实例 const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL || '/', timeout: 12000, // 哀求超时时间 headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', }, }); /** * 哀求拦截器 * 每次哀求前,如果存在token则在哀求头中携带token */ instance.interceptors.request.use( config => { // 登录流程控制中,根据当地是否存在token判定用户的登录环境 const token = store.state.token; if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => Promise.reject(error) ); /** * 响应拦截器 */ instance.interceptors.response.use( response => { // 哀求成功 return response.status === 200 ? Promise.resolve(response) : Promise.reject(response); }, error => { const { response } = error; if (response) { // 哀求已发出,但是不在2xx的范围 errorHandle(response.status, response.data.message); return Promise.reject(response); } else { // 处理断网的环境 if (!window.navigator.onLine) { store.commit('SET_NETWORK_STATUS', false); } else { return Promise.reject(error); } } } ); export default instance;
  2. [size=3]3. api/index.js[/size]
  3. [code]
复制代码
// src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js

  1. [/code] // src/api/user.js import request from '@/utils/request'; import QS from 'qs'; // 根据需求是否引入 qs 模块 /** * user模块接口列表 */ const userAPI = { // 获取用户信息 getUserInfo() { return request.get('/user/info'); }, // 更新用户信息 updateUserInfo(data) { return request.post('/user/update', QS.stringify(data)); }, // 用户登录 login(credentials) { return request.post('/user/login', QS.stringify(credentials)); }, // 用户注册 register(data) { return request.post('/user/register', QS.stringify(data)); }, // 其他接口………… }; export default userAPI;
  2. [size=3]5. store/index.js[/size]
  3. [code]
复制代码
// src/store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { token: localStorage.getItem('token') || '', network: true, }, mutations: { SET_TOKEN(state, token) { state.token = token; if (token) { localStorage.setItem('token', token); } else { localStorage.removeItem('token'); } }, SET_NETWORK_STATUS(state, status) { state.network = status; }, }, actions: {}, modules: {}, });
6. views/UserProfile.vue

  1. [/code] <!-- src/views/UserProfile.vue --> <template> <div> <h1>用户信息</h1> <p v-if="error">{{ error }}</p> <div v-else> <p>用户名: {{ userInfo.name }}</p> <p>邮箱: {{ userInfo.email }}</p> <button @click="saveUserInfo">保存</button> </div> </div> </template> <script> import { getUserInfo, updateUserInfo } from '@/api/user'; import { Toast } from 'vant'; export default { data() { return { userInfo: {}, error: '', }; }, created() { this.fetchUserInfo(); }, methods: { async fetchUserInfo() { try { const response = await getUserInfo(); this.userInfo = response.data; } catch (err) { this.error = '无法获取用户信息'; console.error(err); } }, async saveUserInfo() { try { const response = await updateUserInfo(this.userInfo); Toast.success('用户信息更新成功'); } catch (err) { Toast.fail('更新失败'); console.error(err); } }, }, }; </script>
  2. [size=3]7. views/Refresh.vue[/size]
  3. [code]
复制代码
<!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
8. App.vue

  1. [/code] <!-- src/App.vue --> <template> <div id="app"> <div v-if="!network" class="offline-banner"> <p>您当前处于离线状态</p> <button @click="onRefresh">重新加载</button> </div> <router-view /> </div> </template> <script> import { mapState } from 'vuex'; import { Toast } from 'vant'; export default { computed: { ...mapState(['network']), }, methods: { // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的 onRefresh() { this.$router.replace('/refresh'); }, }, }; </script> <style> .offline-banner { position: fixed; top: 0; width: 100%; background-color: #ff4d4f; color: white; text-align: center; padding: 10px 0; z-index: 9999; } </style>
  2. [size=3]9. main.js[/size]
  3. [code]
复制代码
// src/main.js import Vue from 'vue'; import App from './App.vue'; import router from './router'; import store from './store'; import api from './api'; // 导入 API import { Toast } from 'vant'; import 'vant/lib/index.css'; Vue.config.productionTip = false; // 挂载 API 到 Vue 原型 Vue.prototype.$api = api; // 监听网络状态 window.addEventListener('offline', () => { Toast.fail('网络连接已断开,请检查网络'); store.commit('SET_NETWORK_STATUS', false); }); window.addEventListener('online', () => { Toast.success('网络连接已规复'); store.commit('SET_NETWORK_STATUS', true); }); new Vue({ router, store, render: h => h(App), }).$mount('#app');

总结

通过三层封装 Axios,我们可以或许:


  • 底子层:统一设置 Axios 实例,集中管理拦截器和底子设置。
  • API 层:按业务模块定义 API 接口,结构清楚,易于维护。
  • 服务层:在业务逻辑中调用 API,处理响应数据,提拔代码可读性和复用性。
此外,通过环境切换、错误处理、取消哀求和断网处理等高级优化,进一步提拔了应用的健壮性和用户体验。这种封装方式不仅淘汰了代码重复,进步了开发服从,还增强了代码的可维护性和扩展性。盼望本文能帮助你在项目中更好地使用 Axios,构建高效的 HTTP 哀求管理体系。

   友谊提示:本文中的代码示例基于 Vue.js 和 Vant UI 库,实际项目中可根据具体需求举行调整和优化。
  我:
错了,重新写,要md格式的,按照md的语法写,Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成结构化的HTML。以下是一些基本的Markdown语法: 标题: # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 粗体和斜体: *斜体* _斜体_ **粗体** __粗体__ **_同时粗体和斜体_** ~~删除线~~ 无序列表: - 列表项 * 列表项 + 列表项 有序列表: 1. 第一项 2. 第二项 3. 第三项 链接: [显示文本](URL "标题") 图片: ![替换文本](图片URL "标题") 引用: > 这是一个引用 代码: 行内代码: 代码 代码块: 代码块 表格: | 表头 | 表头 | | ---- | ---- | | 单元 | 单元 | | 单元 | 单元 | 分割线: --- 或者 *** 任务列表: - [x] 已完成项 - [ ] 未完成项 转义字符: 使用反斜杠 \ 来转义特别字符,使其被当作平凡字符处理。 这些是Markdown的基本语法,通过组合使用这些元素,你可以创建特别式丰富的文档。Markdown的语法简便明了,易于学习,同时也被很多平台和编辑器支持。 ,必须按照这样修改
AI:
  1. [/code] [b]Integrating the content[/b]
  2. Taking the content on Axios, I’ll combine it with the initial CSDN blog post, ensuring proper Markdown formatting and clear separation of code and text.
  3. [size=5]深入明白 Axios 三层封装:从零开始构建高效的 HTTP 哀求库[/size]
  4. 在当代前端开发中,[b]Axios[/b] 已成为与后端举行数据交互的首选 HTTP 客户端。它基于 Promise,支持浏览器和 Node.js,拥有丰富的功能,如拦截哀求和响应、取消哀求、自动转换 JSON 等。然而,直接使用 Axios 可能会导致代码重复、难以维护。为相识决这些题目,[b]三层封装[/b] 的模式应运而生。本文将领导你从零开始,逐步构建一个结构清楚、易于维护的 Axios 三层封装方案,让你在项目中轻松管理 HTTP 哀求。
  5. [size=4]目录[/size]
  6. [list=1]
  7. [*]为什么要封装 Axios?
  8. [*]三层封装架构概述
  9. [*]第一层:底子设置(Base Layer)
  10. [list]
  11. [*]安装 Axios
  12. [*]创建 Axios 实例
  13. [*]设置哀求拦截器
  14. [*]设置响应拦截器
  15. [/list]
  16. [*]第二层:API 接口管理(API Layer)
  17. [list]
  18. [*]统一管理 API 接口
  19. [*]模块化 API 接口
  20. [/list]
  21. [*]第三层:服务调用(Service Layer)
  22. [list]
  23. [*]在组件中调用 API
  24. [*]处理响应数据
  25. [/list]
  26. [*]高级优化与扩展
  27. [list]
  28. [*]环境切换
  29. [*]错误处理与提示
  30. [*]取消哀求
  31. [*]断网处理
  32. [/list]
  33. [*]完备代码示例
  34. [*]总结
  35. [/list] [hr] [size=4]为什么要封装 Axios?[/size]
  36. 固然 Axios 功能强大,但直接在项目中使用可能存在以下题目:
  37. [list]
  38. [*][b]代码重复[/b]:每个哀求都需要重复设置,如底子 URL、哀求头等。
  39. [*][b]维护困难[/b]:当需要修改设置时,必须逐一修改每个哀求。
  40. [*][b]缺乏统一管理[/b]:难以集中处理错误、拦截器等逻辑。
  41. [/list] 通过封装 Axios,可以:
  42. [list]
  43. [*][b]简化哀求[/b]:统一设置,淘汰重复代码。
  44. [*][b]集中管理[/b]:统一处理拦截器、错误等,提拔代码可维护性。
  45. [*][b]模块化[/b]:按业务模块管理 API 接口,结构清楚。
  46. [/list] [hr] [size=4]三层封装架构概述[/size]
  47. [b]三层封装[/b] 通常包罗:
  48. [list=1]
  49. [*][b]底子层(Base Layer)[/b]:设置 Axios 实例,包罗拦截器、底子 URL 等。
  50. [*][b]API 层(API Layer)[/b]:定义各个业务模块的 API 接口。
  51. [*][b]服务层(Service Layer)[/b]:在业务逻辑中调用 API 接口,处理响应数据。
  52. [/list] 这种结构使得代码逻辑清楚,易于维护和扩展。
  53. [hr] [size=4]第一层:底子设置(Base Layer)[/size]
  54. 底子层主要负责设置 Axios 实例,包罗底子 URL、哀求和响应拦截器等。
  55. [size=3]安装 Axios[/size]
  56. 起首,通过 npm 安装 Axios:
  57. [code]
复制代码
npm install axios
创建 Axios 实例

在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
  1. [/code] // src/utils/request.js import axios from 'axios'; import store from '@/store'; // 引入 Vuex 存储 import router from '@/router'; // 引入路由 import { Toast } from 'vant'; // 引入 UI 提示库 // 创建 Axios 实例 const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, // 底子 URL,根据环境变量设置 timeout: 5000, // 哀求超时时间 headers: { 'Content-Type': 'application/json;charset=UTF-8', }, });
  2. [size=3]设置哀求拦截器[/size]
  3. 哀求拦截器用于在哀求发送前做一些处理,如添加认证 token。
  4. [code]
复制代码
// 哀求拦截器 instance.interceptors.request.use( config => { // 从 Vuex 获取 token const token = store.state.token; if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } );
设置响应拦截器

响应拦截器用于统一处理响应数据和错误。
  1. [/code] // 响应拦截器 instance.interceptors.response.use( response => { const res = response.data; if (res.code === 200 || res.code === 0) { return res; } else if (res.code === 401) { Toast.fail('登录状态已过期,请重新登录'); store.commit('SET_TOKEN', ''); router.push('/login'); return Promise.reject(new Error('登录状态已过期')); } else { Toast.fail(res.message || '哀求失败'); return Promise.reject(new Error(res.message || '哀求失败')); } }, error => { if (error.response) { const status = error.response.status; switch (status) { case 401: Toast.fail('未授权,请重新登录'); store.commit('SET_TOKEN', ''); router.push('/login'); break; case 403: Toast.fail('拒绝访问'); break; case 404: Toast.fail('哀求资源不存在'); break; case 500: Toast.fail('服务器内部错误'); break; default: Toast.fail(error.response.data.message || '未知错误'); } } else { Toast.fail('网络错误'); } return Promise.reject(error); } ); export default instance;
  2. [hr] [size=4]第二层:API 接口管理(API Layer)[/size]
  3. API 层负责定义各个业务模块的接口,便于集中管理和维护。
  4. [size=3]统一管理 API 接口[/size]
  5. 在 src 目录下创建 api 文件夹,并在此中创建 index.js 作为 API 的统一出口。
  6. [code]
复制代码
// src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
模块化 API 接口

以用户模块为例,创建 user.js 文件定义相干 API 接口。
  1. [/code] // src/api/user.js import request from '@/utils/request'; import QS from 'qs'; // 根据需求是否引入 qs 模块 // 获取用户信息 export const getUserInfo = () => { return request.get('/user/info'); }; // 更新用户信息 export const updateUserInfo = (data) => { return request.post('/user/update', QS.stringify(data)); }; // 用户登录 export const login = (credentials) => { return request.post('/user/login', QS.stringify(credentials)); }; // 用户注册 export const register = (data) => { return request.post('/user/register', QS.stringify(data)); };
  2. 同样地,可以为产品模块、订单模块等创建对应的 API 接口文件。
  3. [hr] [size=4]第三层:服务调用(Service Layer)[/size]
  4. 服务层负责在业务逻辑中调用 API 接口,并处理响应数据。
  5. [size=3]在组件中调用 API[/size]
  6. 以 Vue 组件为例,展示如何调用封装好的 API。
  7. [code]
复制代码
<!-- src/views/UserProfile.vue --> <template> <div> <h1>用户信息</h1> <p v-if="error">{{ error }}</p> <div v-else> <p>用户名: {{ userInfo.name }}</p> <p>邮箱: {{ userInfo.email }}</p> <button @click="saveUserInfo">保存</button> </div> </div> </template> <script> import { getUserInfo, updateUserInfo } from '@/api/user'; import { Toast } from 'vant'; export default { data() { return { userInfo: {}, error: '', }; }, created() { this.fetchUserInfo(); }, methods: { async fetchUserInfo() { try { const response = await getUserInfo(); this.userInfo = response.data; } catch (err) { this.error = '无法获取用户信息'; console.error(err); } }, async saveUserInfo() { try { const response = await updateUserInfo(this.userInfo); Toast.success('用户信息更新成功'); } catch (err) { Toast.fail('更新失败'); console.error(err); } }, }, }; </script>
处理响应数据

在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
  1. [/code] // src/services/userService.js import { getUserInfo, updateUserInfo } from '@/api/user'; export const fetchUserProfile = async () => { try { const response = await getUserInfo(); // 可以在这里举行数据格式化 return response.data; } catch (error) { throw error; } }; export const saveUserProfile = async (profileData) => { try { const response = await updateUserInfo(profileData); return response; } catch (error) { throw error; } };
  2. [hr] [size=4]高级优化与扩展[/size]
  3. 在底子三层封装的底子上,还可以举行以下优化和扩展,进步代码的健壮性和可维护性。
  4. [size=3]环境切换[/size]
  5. 通过环境变量管理不同环境的底子 URL,确保开发、测试和生产环境的设置正确。
  6. [code]
复制代码
// .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
在 request.js 中使用环境变量:
  1. [/code] // src/utils/request.js const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 5000, headers: { 'Content-Type': 'application/json;charset=UTF-8', }, });
  2. [size=3]错误处理与提示[/size]
  3. 统一处理错误信息,并通过 UI 组件库(如 Vant)举行提示,提拔用户体验。
  4. [code]
复制代码
// 响应拦截器中的错误处理部分 if (error.response) { const status = error.response.status; switch (status) { case 401: Toast.fail('未授权,请重新登录'); // 重定向到登录页 store.commit('SET_TOKEN', ''); router.push('/login'); break; case 403: Toast.fail('拒绝访问'); break; case 404: Toast.fail('哀求资源不存在'); break; case 500: Toast.fail('服务器内部错误'); break; default: Toast.fail(error.response.data.message || '未知错误'); } } else { Toast.fail('网络错误'); }
取消哀求

从 Axios v0.22.0 开始,推荐使用 AbortController 来取消哀求,旧的 CancelToken API 被弃用。以下是两种方法的示例:
使用 AbortController

  1. [/code] const controller = new AbortController(); axios.get('/foo/bar', { signal: controller.signal }).then(function(response) { // 处理响应 }).catch(function(error) { if (axios.isCancel(error)) { console.log('哀求被取消', error.message); } else { // 处理错误 } }); // 取消哀求 controller.abort();
  2. [size=2]使用 CancelToken(不推荐)[/size]
  3. [code]
复制代码
let source = axios.CancelToken.source(); axios.get('/users/12345', { cancelToken: source.token }).then(res => { console.log(res); }).catch(err => { if (axios.isCancel(err)) { console.log('哀求被取消', err.message); } else { // 处理错误 } }); // 取消哀求,参数可选,该参数信息会发送到哀求的catch中 source.cancel('取消后的信息');
断网处理

通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
  1. [/code] // src/main.js window.addEventListener('offline', () => { Toast.fail('网络连接已断开,请检查网络'); store.commit('SET_NETWORK_STATUS', false); }); window.addEventListener('online', () => { Toast.success('网络连接已规复'); store.commit('SET_NETWORK_STATUS', true); });
  2. 在 Vue 组件中,根据网络状态显示提示信息。
  3. [code]
复制代码
<!-- src/App.vue --> <template> <div id="app"> <div v-if="!network" class="offline-banner"> <p>您当前处于离线状态</p> <button @click="onRefresh">重新加载</button> </div> <router-view /> </div> </template> <script> import { mapState } from 'vuex'; import { Toast } from 'vant'; export default { computed: { ...mapState(['network']), }, methods: { // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的 onRefresh() { this.$router.replace('/refresh'); }, }, }; </script> <style> .offline-banner { position: fixed; top: 0; width: 100%; background-color: #ff4d4f; color: white; text-align: center; padding: 10px 0; z-index: 9999; } </style>
创建 refresh.vue 页面,用于刷新当前页面。
  1. [/code] <!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
  2. [size=3]取消拦截器[/size]
  3. 偶然候需要在特定环境下移除拦截器,可以通过 eject 方法实现。
  4. [code]
复制代码
const myInterceptor = axios.interceptors.request.use(function (config) { // 拦截逻辑 return config; }); // 取消拦截器 axios.interceptors.request.eject(myInterceptor);

完备代码示例

为了更好地明白,下面提供一个完备的三层封装代码示例。
1. 项目结构

  1. src
  2. ├── api
  3. │   ├── index.js
  4. │   └── user.js
  5. ├── components
  6. ├── router
  7. ├── store
  8. │   └── index.js
  9. ├── utils
  10. │   └── request.js
  11. ├── views
  12. │   ├── UserProfile.vue
  13. │   └── Refresh.vue
  14. └── App.vue
复制代码
2. request.js

  1. [/code] // src/utils/request.js import axios from 'axios'; import store from '@/store'; import router from '@/router'; import { Toast } from 'vant'; /** * 提示函数 * 禁止点击蒙层、显示一秒后关闭 */ const tip = msg => { Toast({ message: msg, duration: 1000, forbidClick: true }); }; /** * 跳转登录页 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面 */ const toLogin = () => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); }; /** * 哀求失败后的错误统一处理 * @param {Number} status 哀求失败的状态码 */ const errorHandle = (status, other) => { // 状态码判定 switch (status) { // 401: 未登录状态,跳转登录页 case 401: toLogin(); break; // 403 token过期 // 打扫token并跳转登录页 case 403: tip('登录过期,请重新登录'); localStorage.removeItem('token'); store.commit('SET_TOKEN', ''); setTimeout(() => { toLogin(); }, 1000); break; // 404哀求不存在 case 404: tip('哀求的资源不存在'); break; default: console.log(other); } }; // 创建 Axios 实例 const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_URL || '/', timeout: 12000, // 哀求超时时间 headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', }, }); /** * 哀求拦截器 * 每次哀求前,如果存在token则在哀求头中携带token */ instance.interceptors.request.use( config => { // 登录流程控制中,根据当地是否存在token判定用户的登录环境 const token = store.state.token; if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => Promise.reject(error) ); /** * 响应拦截器 */ instance.interceptors.response.use( response => { // 哀求成功 return response.status === 200 ? Promise.resolve(response) : Promise.reject(response); }, error => { const { response } = error; if (response) { // 哀求已发出,但是不在2xx的范围 errorHandle(response.status, response.data.message); return Promise.reject(response); } else { // 处理断网的环境 if (!window.navigator.onLine) { store.commit('SET_NETWORK_STATUS', false); } else { return Promise.reject(error); } } } ); export default instance;
  2. [size=3]3. api/index.js[/size]
  3. [code]
复制代码
// src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js

  1. [/code] // src/api/user.js import request from '@/utils/request'; import QS from 'qs'; // 根据需求是否引入 qs 模块 /** * user模块接口列表 */ const userAPI = { // 获取用户信息 getUserInfo() { return request.get('/user/info'); }, // 更新用户信息 updateUserInfo(data) { return request.post('/user/update', QS.stringify(data)); }, // 用户登录 login(credentials) { return request.post('/user/login', QS.stringify(credentials)); }, // 用户注册 register(data) { return request.post('/user/register', QS.stringify(data)); }, // 其他接口………… }; export default userAPI;
  2. [size=3]5. store/index.js[/size]
  3. [code]
复制代码
// src/store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { token: localStorage.getItem('token') || '', network: true, }, mutations: { SET_TOKEN(state, token) { state.token = token; if (token) { localStorage.setItem('token', token); } else { localStorage.removeItem('token'); } }, SET_NETWORK_STATUS(state, status) { state.network = status; }, }, actions: {}, modules: {}, });
6. views/UserProfile.vue

  1. [/code] <!-- src/views/UserProfile.vue --> <template> <div> <h1>用户信息</h1> <p v-if="error">{{ error }}</p> <div v-else> <p>用户名: {{ userInfo.name }}</p> <p>邮箱: {{ userInfo.email }}</p> <button @click="saveUserInfo">保存</button> </div> </div> </template> <script> import { getUserInfo, updateUserInfo } from '@/api/user'; import { Toast } from 'vant'; export default { data() { return { userInfo: {}, error: '', }; }, created() { this.fetchUserInfo(); }, methods: { async fetchUserInfo() { try { const response = await getUserInfo(); this.userInfo = response.data; } catch (err) { this.error = '无法获取用户信息'; console.error(err); } }, async saveUserInfo() { try { const response = await updateUserInfo(this.userInfo); Toast.success('用户信息更新成功'); } catch (err) { Toast.fail('更新失败'); console.error(err); } }, }, }; </script>
  2. [size=3]7. views/Refresh.vue[/size]
  3. [code]
复制代码
<!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
8. App.vue

  1. [/code] <!-- src/App.vue --> <template> <div id="app"> <div v-if="!network" class="offline-banner"> <p>您当前处于离线状态</p> <button @click="onRefresh">重新加载</button> </div> <router-view /> </div> </template> <script> import { mapState } from 'vuex'; import { Toast } from 'vant'; export default { computed: { ...mapState(['network']), }, methods: { // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的 onRefresh() { this.$router.replace('/refresh'); }, }, }; </script> <style> .offline-banner { position: fixed; top: 0; width: 100%; background-color: #ff4d4f; color: white; text-align: center; padding: 10px 0; z-index: 9999; } </style>
  2. [size=3]9. main.js[/size]
  3. [code]
复制代码
// src/main.js import Vue from 'vue'; import App from './App.vue'; import router from './router'; import store from './store'; import api from './api'; // 导入 API import { Toast } from 'vant'; import 'vant/lib/index.css'; Vue.config.productionTip = false; // 挂载 API 到 Vue 原型 Vue.prototype.$api = api; // 监听网络状态 window.addEventListener('offline', () => { Toast.fail('网络连接已断开,请检查网络'); store.commit('SET_NETWORK_STATUS', false); }); window.addEventListener('online', () => { Toast.success('网络连接已规复'); store.commit('SET_NETWORK_STATUS', true); }); new Vue({ router, store, render: h => h(App), }).$mount('#app');

总结

通过三层封装 Axios,我们可以或许:


  • 底子层:统一设置 Axios 实例,集中管理拦截器和底子设置。
  • API 层:按业务模块定义 API 接口,结构清楚,易于维护。
  • 服务层:在业务逻辑中调用 API,处理响应数据,提拔代码可读性和复用性。
此外,通过环境切换、错误处理、取消哀求和断网处理等高级优化,进一步提拔了应用的健壮性和用户体验。这种封装方式不仅淘汰了代码重复,进步了开发服从,还增强了代码的可维护性和扩展性。盼望本文能帮助你在项目中更好地使用 Axios,构建高效的 HTTP 哀求管理体系。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

一给

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表