深入理解 Axios 三层封装:从零开始构建高效的 HTTP 请求库

前言

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

目录

  1. 为什么要封装 Axios?
  2. 三层封装架构概述
  3. 第一层:基础配置(Base Layer)
  4. 第二层:API 接口管理(API Layer)
  5. 第三层:服务调用(Service Layer)
  6. 高级优化与扩展
  7. 完整代码示例
  8. 总结

为什么要封装 Axios?

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

  • 代码重复:每个请求都需要重复配置,如基础 URL、请求头等。
  • 维护困难:当需要修改配置时,必须逐一修改每个请求。
  • 缺乏统一管理:难以集中处理错误、拦截器等逻辑。

通过封装 Axios,可以:

  • 简化请求:统一配置,减少重复代码。
  • 集中管理:统一处理拦截器、错误等,提升代码可维护性。
  • 模块化:按业务模块管理 API 接口,结构清晰。

三层封装架构概述

三层封装 通常包括:

  1. 基础层(Base Layer):配置 Axios 实例,包括拦截器、基础 URL 等。
  2. API 层(API Layer):定义各个业务模块的 API 接口。
  3. 服务层(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 "标题") 引用: > 这是一个引用 代码: 行内代码: 代码 代码块: 代码块 表格: | 表头 | 表头 | | ---- | ---- | | 单元 | 单元 | | 单元 | 单元 | 分割线: --- 或者 *** 任务列表: - [x] 已完成项 - [ ] 未完成项 转义字符: 使用反斜杠 \ 来转义特殊字符,使其被当作普通字符处理。 这些是Markdown的基本语法,通过组合使用这些元素,你可以创建出格式丰富的文档。Markdown的语法简洁明了,易于学习,同时也被许多平台和编辑器支持。 ,必须按照这样修改

AI:

 

Integrating the content
Taking the content on Axios, I’ll combine it with the initial 优快云 blog post, ensuring proper Markdown formatting and clear separation of code and text.

深入理解 Axios 三层封装:从零开始构建高效的 HTTP 请求库

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

目录

  1. 为什么要封装 Axios?
  2. 三层封装架构概述
  3. 第一层:基础配置(Base Layer)
  4. 第二层:API 接口管理(API Layer)
  5. 第三层:服务调用(Service Layer)
  6. 高级优化与扩展
  7. 完整代码示例
  8. 总结

为什么要封装 Axios?

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

  • 代码重复:每个请求都需要重复配置,如基础 URL、请求头等。
  • 维护困难:当需要修改配置时,必须逐一修改每个请求。
  • 缺乏统一管理:难以集中处理错误、拦截器等逻辑。

通过封装 Axios,可以:

  • 简化请求:统一配置,减少重复代码。
  • 集中管理:统一处理拦截器、错误等,提升代码可维护性。
  • 模块化:按业务模块管理 API 接口,结构清晰。

三层封装架构概述

三层封装 通常包括:

  1. 基础层(Base Layer):配置 Axios 实例,包括拦截器、基础 URL 等。
  2. API 层(API Layer):定义各个业务模块的 API 接口。
  3. 服务层(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 请求管理系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值