1. 为什么需要封装 Vue 接口?
在 Vue.js 项目中,直接在每个组件中调用 axios.get() 或 fetch() 会导致以下问题:
- 代码重复:相同的 API 基础 URL、请求头配置在每个请求中重复编写。
- 难以维护:当后端接口地址变更或需要统一添加认证逻辑时,需要修改所有相关组件。
- 错误处理分散:每个组件都需要单独处理网络错误、超时、权限验证等。
- 缺乏统一拦截器:无法全局处理请求/响应,如添加 loading 状态、统一错误提示。
因此,对 HTTP 请求进行统一封装是 Vue 项目架构中必不可少的一环。
2. 基础封装:创建统一的请求实例
首先,我们使用 Axios(最流行的 HTTP 客户端)创建一个基础请求实例。
javascript
// src/utils/request.js
import axios from 'axios';
// 创建 axios 实例
const request = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL || '/api', // 基础 URL
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
// 请求拦截器
request.interceptors.request.use(
config => {
// 在发送请求之前做些什么,例如添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
response => {
// 对响应数据做点什么
const res = response.data;
// 假设后端返回格式为 { code: 200, data: {}, message: 'success' }
if (res.code === 200) {
return res.data;
} else {
// 业务错误处理
return Promise.reject(new Error(res.message || '请求失败'));
}
},
error => {
// 对响应错误做点什么
if (error.response) {
// 服务器返回了错误状态码
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
router.push('/login');
break;
case 403:
// 权限不足
Message.error('权限不足,请联系管理员');
break;
case 404:
// 接口不存在
Message.error('请求的资源不存在');
break;
case 500:
// 服务器内部错误
Message.error('服务器内部错误,请稍后重试');
break;
default:
Message.error(error.response.data.message || '请求失败');
}
} else if (error.request) {
// 请求发送了但没有收到响应
Message.error('网络异常,请检查网络连接');
} else {
// 请求配置出错
Message.error('请求配置错误');
}
return Promise.reject(error);
}
);
export default request;
3. 进阶封装:按模块组织 API
将不同业务模块的 API 集中管理,提高代码可读性和维护性。
javascript
// src/api/user.js
import request from '@/utils/request';
// 用户相关接口
export const userApi = {
// 获取用户列表
getUsers(params) {
return request({
url: '/users',
method: 'get',
params
});
},
// 获取用户详情
getUserById(id) {
return request({
url: `/users/${id}`,
method: 'get'
});
},
// 创建用户
createUser(data) {
return request({
url: '/users',
method: 'post',
data
});
},
// 更新用户
updateUser(id, data) {
return request({
url: `/users/${id}`,
method: 'put',
data
});
},
// 删除用户
deleteUser(id) {
return request({
url: `/users/${id}`,
method: 'delete'
});
}
};
// src/api/product.js
import request from '@/utils/request';
// 商品相关接口
export const productApi = {
// 获取商品列表
getProducts(params) {
return request({
url: '/products',
method: 'get',
params
});
},
// 获取商品详情
getProductById(id) {
return request({
url: `/products/${id}`,
method: 'get'
});
}
};
4. 在 Vue 组件中使用封装好的 API
在 Vue 组件中,我们可以优雅地调用封装好的 API。
vue
<template>
<div>
<h1>用户列表</h1>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
<button @click="loadUsers">加载用户</button>
</div>
</template>
<script>
import { userApi } from '@/api/user';
export default {
name: 'UserList',
data() {
return {
users: []
};
},
methods: {
async loadUsers() {
try {
// 调用封装好的 API
this.users = await userApi.getUsers({
page: 1,
pageSize: 10
});
} catch (error) {
console.error('加载用户失败:', error);
}
}
},
mounted() {
this.loadUsers();
}
};
</script>
5. 高级技巧:使用 TypeScript 增强类型安全
在 TypeScript 项目中,我们可以为 API 添加类型定义,获得更好的开发体验。
typescript
// src/types/api.ts
// 用户相关类型
export interface User {
id: number;
name: string;
email: string;
role: string;
}
export interface UserListParams {
page?: number;
pageSize?: number;
keyword?: string;
}
export interface UserListResponse {
list: User[];
total: number;
}
// src/api/user.ts
import request from '@/utils/request';
import type { User, UserListParams, UserListResponse } from '@/types/api';
export const userApi = {
// 获取用户列表(带类型)
getUsers(params?: UserListParams): Promise<UserListResponse> {
return request({
url: '/users',
method: 'get',
params
});
},
// 获取用户详情(带类型)
getUserById(id: number): Promise<User> {
return request({
url: `/users/${id}`,
method: 'get'
});
},
// 创建用户(带类型)
createUser(data: Omit<User, 'id'>): Promise<User> {
return request({
url: '/users',
method: 'post',
data
});
}
};
6. 最佳实践总结
- 统一配置:创建统一的请求实例,配置 baseURL、超时时间、请求头等。
- 拦截器管理:使用请求/响应拦截器统一处理 token、loading、错误提示等。
- 模块化组织:按业务模块组织 API,提高代码可维护性。
- 错误处理:在拦截器中统一处理 HTTP 错误和业务错误。
- 类型安全:在 TypeScript 项目中为 API 添加类型定义。
- 环境配置:使用环境变量管理不同环境的 API 地址。
- 取消请求:对于频繁触发的请求(如搜索),使用 CancelToken 取消之前的请求。
7. 常见问题与解决方案
7.1 如何处理重复请求?
使用 Axios 的 CancelToken 或 AbortController 取消重复请求。
javascript
// 使用 CancelToken
let cancelTokenSource = null;
export const searchApi = {
search(keyword) {
// 取消之前的请求
if (cancelTokenSource) {
cancelTokenSource.cancel('取消重复请求');
}
// 创建新的 CancelToken
cancelTokenSource = axios.CancelToken.source();
return request({
url: '/search',
method: 'get',
params: { keyword },
cancelToken: cancelTokenSource.token
});
}
};
7.2 如何添加请求重试机制?
可以在响应拦截器中实现重试逻辑。
javascript
// 响应拦截器中添加重试逻辑
request.interceptors.response.use(
response => {
return response;
},
async error => {
const config = error.config;
// 设置重试次数
config.__retryCount = config.__retryCount || 0;
// 检查是否达到最大重试次数
if (config.__retryCount >= 3) {
return Promise.reject(error);
}
// 增加重试计数
config.__retryCount += 1;
// 创建新的 promise 来处理指数退避
const backoff = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000 * config.__retryCount);
});
// 等待退避时间后重试请求
await backoff;
return request(config);
}
);