媒介
在当代前端开发中,Axios 已成为与后端举行数据交互的首选 HTTP 客户端。它基于 Promise,支持浏览器和 Node.js,拥有丰富的功能,如拦截哀求和响应、取消哀求、自动转换 JSON 等。然而,直接使用 Axios 可能会导致代码重复、难以维护。为相识决这些题目,三层封装 的模式应运而生。本文将领导你从零开始,逐步构建一个结构清楚、易于维护的 Axios 三层封装方案,让你在项目中轻松管理 HTTP 哀求。
目录
- 为什么要封装 Axios?
- 三层封装架构概述
- 第一层:底子设置(Base Layer)
- 安装 Axios
- 创建 Axios 实例
- 设置哀求拦截器
- 设置响应拦截器
- 第二层:API 接口管理(API Layer)
- 第三层:服务调用(Service Layer)
- 高级优化与扩展
- 完备代码示例
- 总结
为什么要封装 Axios?
固然 Axios 功能强大,但直接在项目中使用可能存在以下题目:
- 代码重复:每个哀求都需要重复设置,如底子 URL、哀求头等。
- 维护困难:当需要修改设置时,必须逐一修改每个哀求。
- 缺乏统一管理:难以集中处理错误、拦截器等逻辑。
通过封装 Axios,可以:
- 简化哀求:统一设置,淘汰重复代码。
- 集中管理:统一处理拦截器、错误等,提拔代码可维护性。
- 模块化:按业务模块管理 API 接口,结构清楚。
三层封装架构概述
三层封装 通常包罗:
- 底子层(Base Layer):设置 Axios 实例,包罗拦截器、底子 URL 等。
- API 层(API Layer):定义各个业务模块的 API 接口。
- 服务层(Service Layer):在业务逻辑中调用 API 接口,处理响应数据。
这种结构使得代码逻辑清楚,易于维护和扩展。
第一层:底子设置(Base Layer)
底子层主要负责设置 Axios 实例,包罗底子 URL、哀求和响应拦截器等。
安装 Axios
起首,通过 npm 安装 Axios:
- [/code] npm install axios
- [size=3]创建 Axios 实例[/size]
- 在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
- [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。
- [/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); } );
- [size=3]设置响应拦截器[/size]
- 响应拦截器用于统一处理响应数据和错误。
- [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 的统一出口。
- [/code] // src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
- [size=3]模块化 API 接口[/size]
- 以用户模块为例,创建 user.js 文件定义相干 API 接口。
- [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。
- [/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>
- [size=3]处理响应数据[/size]
- 在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
- [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,确保开发、测试和生产环境的设置正确。
- [/code] // .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
- 在 request.js 中使用环境变量:
- [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)举行提示,提拔用户体验。
- [/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('网络错误'); }
- [size=3]取消哀求[/size]
- 从 Axios v0.22.0 开始,推荐使用 [b]AbortController[/b] 来取消哀求,旧的 [b]CancelToken[/b] API 被弃用。以下是两种方法的示例:
- [size=2]使用 AbortController[/size]
- [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(不推荐)
- [/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('取消后的信息');
- [size=3]断网处理[/size]
- 通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
- [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 组件中,根据网络状态显示提示信息。
- [/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 页面,用于刷新当前页面。
- [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 方法实现。
- [/code] const myInterceptor = axios.interceptors.request.use(function (config) { // 拦截逻辑 return config; }); // 取消拦截器 axios.interceptors.request.eject(myInterceptor);
- [hr] [size=4]完备代码示例[/size]
- 为了更好地明白,下面提供一个完备的三层封装代码示例。
- [size=3]1. 项目结构[/size]
- [code]src
- ├── api
- │ ├── index.js
- │ └── user.js
- ├── components
- ├── router
- ├── store
- │ └── index.js
- ├── utils
- │ └── request.js
- ├── views
- │ ├── UserProfile.vue
- │ └── Refresh.vue
- └── App.vue
复制代码 2. request.js
- [/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;
- [size=3]3. api/index.js[/size]
- [code]
复制代码 // src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js
- [/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;
- [size=3]5. store/index.js[/size]
- [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
- [/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>
- [size=3]7. views/Refresh.vue[/size]
- [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
- [/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>
- [size=3]9. main.js[/size]
- [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:
- [/code] [b]Integrating the content[/b]
- 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.
- [size=5]深入明白 Axios 三层封装:从零开始构建高效的 HTTP 哀求库[/size]
- 在当代前端开发中,[b]Axios[/b] 已成为与后端举行数据交互的首选 HTTP 客户端。它基于 Promise,支持浏览器和 Node.js,拥有丰富的功能,如拦截哀求和响应、取消哀求、自动转换 JSON 等。然而,直接使用 Axios 可能会导致代码重复、难以维护。为相识决这些题目,[b]三层封装[/b] 的模式应运而生。本文将领导你从零开始,逐步构建一个结构清楚、易于维护的 Axios 三层封装方案,让你在项目中轻松管理 HTTP 哀求。
- [size=4]目录[/size]
- [list=1]
- [*]为什么要封装 Axios?
- [*]三层封装架构概述
- [*]第一层:底子设置(Base Layer)
- [list]
- [*]安装 Axios
- [*]创建 Axios 实例
- [*]设置哀求拦截器
- [*]设置响应拦截器
- [/list]
- [*]第二层:API 接口管理(API Layer)
- [list]
- [*]统一管理 API 接口
- [*]模块化 API 接口
- [/list]
- [*]第三层:服务调用(Service Layer)
- [list]
- [*]在组件中调用 API
- [*]处理响应数据
- [/list]
- [*]高级优化与扩展
- [list]
- [*]环境切换
- [*]错误处理与提示
- [*]取消哀求
- [*]断网处理
- [/list]
- [*]完备代码示例
- [*]总结
- [/list] [hr] [size=4]为什么要封装 Axios?[/size]
- 固然 Axios 功能强大,但直接在项目中使用可能存在以下题目:
-
- [list]
- [*][b]代码重复[/b]:每个哀求都需要重复设置,如底子 URL、哀求头等。
- [*][b]维护困难[/b]:当需要修改设置时,必须逐一修改每个哀求。
- [*][b]缺乏统一管理[/b]:难以集中处理错误、拦截器等逻辑。
- [/list] 通过封装 Axios,可以:
-
- [list]
- [*][b]简化哀求[/b]:统一设置,淘汰重复代码。
- [*][b]集中管理[/b]:统一处理拦截器、错误等,提拔代码可维护性。
- [*][b]模块化[/b]:按业务模块管理 API 接口,结构清楚。
- [/list] [hr] [size=4]三层封装架构概述[/size]
- [b]三层封装[/b] 通常包罗:
- [list=1]
- [*][b]底子层(Base Layer)[/b]:设置 Axios 实例,包罗拦截器、底子 URL 等。
- [*][b]API 层(API Layer)[/b]:定义各个业务模块的 API 接口。
- [*][b]服务层(Service Layer)[/b]:在业务逻辑中调用 API 接口,处理响应数据。
- [/list] 这种结构使得代码逻辑清楚,易于维护和扩展。
- [hr] [size=4]第一层:底子设置(Base Layer)[/size]
- 底子层主要负责设置 Axios 实例,包罗底子 URL、哀求和响应拦截器等。
- [size=3]安装 Axios[/size]
- 起首,通过 npm 安装 Axios:
- [code]
复制代码 npm install axios
创建 Axios 实例
在项目中创建一个 utils 文件夹,并在此中创建 request.js 文件用于封装 Axios。
- [/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', }, });
- [size=3]设置哀求拦截器[/size]
- 哀求拦截器用于在哀求发送前做一些处理,如添加认证 token。
- [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); } );
设置响应拦截器
响应拦截器用于统一处理响应数据和错误。
- [/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;
- [hr] [size=4]第二层:API 接口管理(API Layer)[/size]
- API 层负责定义各个业务模块的接口,便于集中管理和维护。
- [size=3]统一管理 API 接口[/size]
- 在 src 目录下创建 api 文件夹,并在此中创建 index.js 作为 API 的统一出口。
- [code]
复制代码 // src/api/index.js import user from './user'; import product from './product'; // 引入其他模块的 API 接口 export default { user, product, // 导出其他模块 };
模块化 API 接口
以用户模块为例,创建 user.js 文件定义相干 API 接口。
- [/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 接口文件。
- [hr] [size=4]第三层:服务调用(Service Layer)[/size]
- 服务层负责在业务逻辑中调用 API 接口,并处理响应数据。
- [size=3]在组件中调用 API[/size]
- 以 Vue 组件为例,展示如何调用封装好的 API。
- [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>
处理响应数据
在服务层,你可以根据需要对响应数据举行进一步处理,例如数据格式化、错误处理等。
- [/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; } };
- [hr] [size=4]高级优化与扩展[/size]
- 在底子三层封装的底子上,还可以举行以下优化和扩展,进步代码的健壮性和可维护性。
- [size=3]环境切换[/size]
- 通过环境变量管理不同环境的底子 URL,确保开发、测试和生产环境的设置正确。
- [code]
复制代码 // .env.development VUE_APP_BASE_URL=http://localhost:3000/api // .env.production VUE_APP_BASE_URL=https://api.yourdomain.com
在 request.js 中使用环境变量:
- [/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', }, });
- [size=3]错误处理与提示[/size]
- 统一处理错误信息,并通过 UI 组件库(如 Vant)举行提示,提拔用户体验。
- [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
- [/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();
- [size=2]使用 CancelToken(不推荐)[/size]
- [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('取消后的信息');
断网处理
通过监听浏览器的在线状态,处理断网环境,提拔应用的健壮性。
- [/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 组件中,根据网络状态显示提示信息。
- [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 页面,用于刷新当前页面。
- [/code] <!-- src/views/Refresh.vue --> <template> <div></div> </template> <script> export default { beforeRouteEnter(to, from, next) { next(vm => { vm.$router.replace(from.fullPath); }); }, }; </script>
- [size=3]取消拦截器[/size]
- 偶然候需要在特定环境下移除拦截器,可以通过 eject 方法实现。
- [code]
复制代码 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
- [/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;
- [size=3]3. api/index.js[/size]
- [code]
复制代码 // src/api/index.js import user from './user'; // 导入其他模块的 API 接口 export default { user, // 导出其他模块 };
4. api/user.js
- [/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;
- [size=3]5. store/index.js[/size]
- [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
- [/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>
- [size=3]7. views/Refresh.vue[/size]
- [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
- [/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>
- [size=3]9. main.js[/size]
- [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企服之家,中国第一个企服评测及商务社交产业平台。 |