Vue 中的自定义网络层实现:封装 Fetch/Axios,实现统一的错误处理与认证逻辑
大家好,今天我们来聊聊在 Vue 项目中如何构建一个健壮且可维护的自定义网络层。网络层是连接前端应用和后端服务的桥梁,一个良好的网络层设计可以极大地提升项目的稳定性和开发效率。本次讲座将围绕封装 Fetch 或 Axios,实现统一的错误处理和认证逻辑展开。
1. 为什么需要自定义网络层?
在小型项目中,直接在组件中使用 Fetch 或 Axios 似乎也能满足需求。但随着项目规模的扩大,这种方式会带来以下问题:
- 代码重复: 每个组件都需要编写相似的网络请求代码,包括请求头设置、错误处理等。
- 维护困难: 如果后端接口发生变化,需要在各个组件中修改代码,维护成本高昂。
- 缺乏统一管理: 难以对请求进行统一的配置和管理,例如设置超时时间、请求拦截器等。
- 安全问题: 认证逻辑分散在各个组件中,容易出现安全漏洞。
因此,我们需要一个自定义的网络层,将网络请求相关的逻辑进行封装,提供统一的接口和处理机制,从而提高代码的可重用性、可维护性和安全性。
2. 技术选型:Fetch vs. Axios
在选择网络请求库时,Fetch 和 Axios 是两个常见的选择。
| 特性 | Fetch | Axios |
|---|---|---|
| 内置/第三方 | 内置 | 第三方 |
| 浏览器兼容性 | 较好,但需要 polyfill 支持低版本浏览器 | 较好 |
| 请求拦截器 | 不支持 | 支持 |
| 响应拦截器 | 不支持 | 支持 |
| 自动转换 JSON | 需要手动转换 | 自动转换 |
| 错误处理 | 需要手动处理 HTTP 错误状态码 | 可以自动处理 HTTP 错误状态码 |
| 取消请求 | 需要使用 AbortController | 支持 |
| 文件上传 | 相对复杂 | 相对简单 |
选择建议:
- 如果项目对浏览器兼容性要求很高,且不需要复杂的拦截器功能,可以使用
Fetch,并配合polyfill。 - 如果项目需要请求和响应拦截器、自动转换 JSON、更方便的错误处理,以及文件上传等功能,建议使用
Axios。
本次讲座将以 Axios 为例,演示如何构建自定义网络层。
3. 网络层设计与实现
我们的网络层主要包含以下几个模块:
- Axios 实例创建与配置: 创建一个
Axios实例,并设置全局配置,例如baseURL、timeout、headers等。 - 请求拦截器: 在请求发送前进行拦截,例如添加认证信息、记录请求日志等。
- 响应拦截器: 在接收到响应后进行拦截,例如处理错误状态码、转换数据格式等。
- 统一的错误处理: 捕获网络请求中发生的错误,并进行统一的处理,例如显示错误提示、跳转到错误页面等。
- API 方法封装: 将不同的 API 请求封装成独立的函数,方便调用和管理。
3.1 创建 Axios 实例并配置
首先,我们需要安装 Axios:
npm install axios
然后,创建一个名为 request.js 的文件,用于创建和配置 Axios 实例:
import axios from 'axios';
// 创建 Axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // API 基础路径, 可以在 .env 文件中配置
timeout: 5000, // 请求超时时间
});
export default service;
在 .env 文件中配置 VUE_APP_BASE_API:
VUE_APP_BASE_API = '/api' // 后端 API 地址
3.2 请求拦截器
请求拦截器可以在请求发送前进行拦截,例如添加认证信息、记录请求日志等。
import axios from 'axios';
import store from '@/store'; // 引入 Vuex store
import { getToken } from '@/utils/auth'; // 引入 token 管理工具
// 创建 Axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000,
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 在请求发送前做一些处理
if (store.getters.token) {
// 如果存在 token,则添加 `Authorization` 请求头
config.headers['Authorization'] = 'Bearer ' + getToken();
}
return config;
},
error => {
// 处理请求错误
console.log(error); // for debug
return Promise.reject(error);
}
);
export default service;
3.3 响应拦截器
响应拦截器可以在接收到响应后进行拦截,例如处理错误状态码、转换数据格式等。
import axios from 'axios';
import store from '@/store';
import { getToken } from '@/utils/auth';
import { Message } from 'element-ui'; // 引入 Element UI 的 Message 组件
// 创建 Axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000,
});
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken();
}
return config;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 2xx 范围内的状态码都会触发该函数
const res = response.data;
// 如果自定义状态码不是 20000,则认为有错误
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
});
// 50008: 非法 Token; 50012: 其他客户端登录; 50014: Token 过期;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// 重新登录
store.dispatch('user/resetToken').then(() => {
location.reload();
});
}
return Promise.reject(new Error(res.message || 'Error'));
} else {
return res;
}
},
error => {
// 超出 2xx 范围的状态码都会触发该函数
console.log('err' + error); // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
});
return Promise.reject(error);
}
);
export default service;
3.4 统一的错误处理
在响应拦截器中,我们对不同类型的错误进行了处理:
- 自定义错误状态码: 根据后端返回的
code值,判断是否发生了错误。如果code不是20000,则显示错误提示信息。 - Token 过期或失效: 如果
code是50008、50012或50014,则表示 Token 过期或失效,需要重新登录。 - 其他错误: 显示
Axios返回的错误信息。
3.5 API 方法封装
为了方便调用和管理 API 请求,我们可以将不同的 API 请求封装成独立的函数。创建一个 api 文件夹,并在其中创建不同的 API 文件,例如 user.js:
import request from '@/utils/request';
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
});
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
});
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
});
}
4. 在组件中使用网络层
在组件中,我们可以直接调用 API 文件中封装的函数来发起网络请求:
<template>
<div>
<button @click="handleLogin">Login</button>
</div>
</template>
<script>
import { login } from '@/api/user';
export default {
methods: {
async handleLogin() {
try {
const response = await login({
username: 'admin',
password: 'password'
});
console.log(response); // 处理登录成功后的逻辑
} catch (error) {
console.error(error); // 处理登录失败后的逻辑
}
}
}
};
</script>
5. 其他考虑因素
- 取消请求:
Axios提供了CancelToken用于取消请求。可以在组件销毁时取消未完成的请求,避免内存泄漏。 - 文件上传: 可以使用
Axios的FormData对象进行文件上传。 - 数据缓存: 可以使用
Axios的adapter选项,结合localStorage或sessionStorage实现数据缓存。 - TypeScript 支持: 如果项目使用 TypeScript,可以为
Axios实例、请求和响应数据定义类型,提高代码的可维护性。
代码示例:取消请求
import axios from 'axios';
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const request = axios.get('/api/data', {
cancelToken: source.token
}).then(response => {
// 处理响应数据
}).catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
// 处理其他错误
}
});
// 取消请求
source.cancel('Operation canceled by the user.');
代码示例:文件上传
import axios from 'axios';
const formData = new FormData();
formData.append('file', file);
formData.append('name', 'myfile');
axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
// 处理响应数据
}).catch(error => {
// 处理错误
});
6. 总结
自定义网络层是构建健壮 Vue 应用的关键。通过封装 Axios,我们可以实现统一的错误处理、认证逻辑和 API 管理,提高代码的可重用性、可维护性和安全性。在实际项目中,可以根据具体需求对网络层进行定制和扩展。
构建网络层,提升项目质量
构建一个好的网络层是提升项目质量的重要一步,它可以减少重复代码,统一处理错误,方便管理API,让我们的 Vue 项目更加健壮和易于维护。
灵活运用拦截器,处理认证与日志
请求和响应拦截器是网络层的核心功能,我们可以利用它们添加认证信息、记录请求日志、转换数据格式等,从而更好地控制网络请求的行为。
持续优化与扩展,适应变化的需求
随着项目的不断发展,我们的网络层也需要不断优化和扩展,例如支持取消请求、文件上传、数据缓存等,以适应不断变化的需求。
更多IT精英技术系列讲座,到智猿学院