一给 发表于 2024-11-17 03:08:33

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

媒介

在当代前端开发中,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:
npm install axios
创建 Axios 实例

在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
// 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。
// 哀求拦截器 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); } );
设置响应拦截器

响应拦截器用于统一处理响应数据和错误。
// 响应拦截器 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 的统一出口。
// src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
模块化 API 接口

以用户模块为例,创建 user.js 文件定义相干 API 接口。
// 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。
<!-- 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>
处理响应数据

在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
// 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,确保开发、测试和生产环境的设置正确。
// .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
在 request.js 中使用环境变量:
// 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)举行提示,提拔用户体验。
// 响应拦截器中的错误处理部分 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

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(不推荐)

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('取消后的信息');
断网处理

通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
// 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 组件中,根据网络状态显示提示信息。
<!-- 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 页面,用于刷新当前页面。
<!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
取消拦截器

偶然候需要在特定环境下移除拦截器,可以通过 eject 方法实现。
const myInterceptor = axios.interceptors.request.use(function (config) { // 拦截逻辑 return config; }); // 取消拦截器 axios.interceptors.request.eject(myInterceptor);
完备代码示例

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

src
├── api
│   ├── index.js
│   └── user.js
├── components
├── router
├── store
│   └── index.js
├── utils
│   └── request.js
├── views
│   ├── UserProfile.vue
│   └── Refresh.vue
└── App.vue
2. request.js

// 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;
3. api/index.js

// src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js

// 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;
5. store/index.js

// 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

<!-- 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>
7. views/Refresh.vue

<!-- 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

<!-- 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>
9. main.js

// 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 "标题") 引用: > 这是一个引用 代码: 行内代码: 代码 代码块: 代码块 表格: | 表头 | 表头 | | ---- | ---- | | 单元 | 单元 | | 单元 | 单元 | 分割线: --- 或者 *** 任务列表: - 已完成项 - [ ] 未完成项 转义字符: 使用反斜杠 \ 来转义特别字符,使其被当作平凡字符处理。 这些是Markdown的基本语法,通过组合使用这些元素,你可以创建特别式丰富的文档。Markdown的语法简便明了,易于学习,同时也被很多平台和编辑器支持。 ,必须按照这样修改
AI:
Integrating the content
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.
深入明白 Axios 三层封装:从零开始构建高效的 HTTP 哀求库

在当代前端开发中,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:
npm install axios
创建 Axios 实例

在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
// 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。
// 哀求拦截器 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); } );
设置响应拦截器

响应拦截器用于统一处理响应数据和错误。
// 响应拦截器 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 的统一出口。
// src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
模块化 API 接口

以用户模块为例,创建 user.js 文件定义相干 API 接口。
// 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。
<!-- 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>
处理响应数据

在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
// 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,确保开发、测试和生产环境的设置正确。
// .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
在 request.js 中使用环境变量:
// 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)举行提示,提拔用户体验。
// 响应拦截器中的错误处理部分 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

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(不推荐)

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('取消后的信息');
断网处理

通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
// 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 组件中,根据网络状态显示提示信息。
<!-- 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 页面,用于刷新当前页面。
<!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
取消拦截器

偶然候需要在特定环境下移除拦截器,可以通过 eject 方法实现。
const myInterceptor = axios.interceptors.request.use(function (config) { // 拦截逻辑 return config; }); // 取消拦截器 axios.interceptors.request.eject(myInterceptor);
完备代码示例

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

src
├── api
│   ├── index.js
│   └── user.js
├── components
├── router
├── store
│   └── index.js
├── utils
│   └── request.js
├── views
│   ├── UserProfile.vue
│   └── Refresh.vue
└── App.vue
2. request.js

// 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;
3. api/index.js

// src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js

// 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;
5. store/index.js

// 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

<!-- 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>
7. views/Refresh.vue

<!-- 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

<!-- 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>
9. main.js

// 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企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 深入明白 Axios 三层封装:从零开始构建高效的 HTTP 哀求库