vue3+vite+TS的axios二次封装和api请求
'# vue3+vite+TS的axios二次封装和api请求
一、背景与问题
在现代前端开发中,axios作为主流的HTTP请求库,其功能强大且灵活。但在实际项目中,直接使用axios存在诸多重复性工作:如统一的请求拦截、响应拦截、错误处理、请求头管理、超时控制等。对于大型项目来说,这种重复劳动会导致代码冗余和维护困难。
以一个典型场景为例:在开发一个电商系统时,每个API请求都需要携带token、设置Content-Type、处理超时、统一的错误提示。若不进行封装,每个请求都需要重复编写这些逻辑。此外,当需要支持接口mock测试时,还需要额外的处理逻辑。
二、基本原理
axios的二次封装核心在于以下三个层面:
- 请求拦截器:在请求发送前统一处理配置,如添加token、设置请求头、处理参数
- 响应拦截器:在收到响应后统一处理数据,如解析响应体、处理错误码
- 封装统一的请求方法:将基础请求方法抽象成可复用的接口,如get、post等
其底层原理基于axios的interceptors机制,通过注册拦截器函数来修改请求配置和响应数据。在TypeScript中,需要通过类型声明文件定义接口和类型,确保类型安全。
三、环境准备
创建Vite+Vue3+TypeScript项目:
npm create vue@latest
# 选择以下选项
? Project name: my-project
? UI framework: Vue 3
? Typescript: Yes
? CSS preprocessor: CSS
? Need ESLint: Yes
? Need Vitest: No安装axios和相关依赖:
npm install axios四、核心实现
1. 创建axios实例
// src/utils/axios.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
// 定义请求拦截器类型
interface RequestInterceptors {
requestIntercept: (config: AxiosRequestConfig) => AxiosRequestConfig;
responseIntercept: (response: AxiosResponse) => AxiosResponse;
}
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: '/api', // 基础URL
timeout: 10000, // 超时时间
withCredentials: true, // 是否发送跨域请求时携带cookie
});
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 1. 添加token到请求头
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
// 2. 设置Content-Type
config.headers['Content-Type'] = 'application/json';
// 3. 处理请求参数
if (config.method === 'post' && config.data) {
config.data = JSON.stringify(config.data);
}
return config;
},
(error: AxiosError) => {
// 请求拦截错误处理
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 1. 处理响应数据
const { data } = response;
// 2. 响应成功时的处理
if (data.code === 200) {
return data.data;
}
// 3. 响应失败时的处理
return Promise.reject(data.message || '服务器响应异常');
},
(error: AxiosError) => {
// 响应拦截错误处理
if (error.response) {
// 接收到服务器响应,但状态码不在2xx范围内
console.error('响应错误:', error.response.status);
return Promise.reject(error.response.data || '服务器响应异常');
} else if (error.request) {
// 没有收到响应
console.error('请求未收到响应:', error.request);
return Promise.reject('请求未收到响应');
} else {
// 请求设置错误
console.error('请求设置错误:', error.message);
return Promise.reject('请求设置错误');
}
}
);
export default service;关键代码解释:
AxiosInstance类型声明确保类型安全withCredentials设置为true支持跨域携带cookie- 请求拦截器处理token、Content-Type、参数序列化
- 响应拦截器统一处理成功/失败响应,返回标准化数据
2. 封装统一的请求方法
// src/utils/axios.ts
// 继续上面的代码...
// 封装请求方法
export const request = <T>(config: AxiosRequestConfig): Promise<T> => {
return new Promise((resolve, reject) => {
service.request(config)
.then((data: T) => {
resolve(data);
})
.catch((error: AxiosError) => {
reject(error.message);
});
});
};
// 封装get请求
export const get = <T>(url: string, params?: any): Promise<T> => {
return request<T>({
url,
method: 'get',
params
});
};
// 封装post请求
export const post = <T>(url: string, data?: any): Promise<T> => {
return request<T>({
url,
method: 'post',
data
});
};关键代码解释:
request方法封装了通用请求逻辑get和post方法作为快捷入口,简化调用- 使用泛型
<T>确保类型安全
3. 错误处理与异常捕获
// src/utils/axios.ts
// 继续上面的代码...
// 错误处理函数
export const handleRequestError = (error: string) => {
console.error('请求错误:', error);
alert(`请求出错: ${error}`);
return Promise.reject(error);
};五、完整案例
创建一个登录功能的完整案例:
1. 前端代码
<!-- src/views/Login.vue -->
<template>
<div class="login-container">
<h2>用户登录</h2>
<el-form :model="loginForm" label-width="80px" @submit.prevent="handleSubmit">
<el-form-item label="用户名">
<el-input v-model="loginForm.username" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="loginForm.password" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" native-type="submit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { post } from '@/utils/axios';
const loginForm = ref({
username: '',
password: ''
});
const handleSubmit = async () => {
try {
const response = await post('/login', {
username: loginForm.value.username,
password: loginForm.value.password
});
if (response) {
alert('登录成功');
// 保存token到本地存储
localStorage.setItem('token', response.token);
}
} catch (error) {
handleRequestError(error as string);
}
};
</script>2. 后端模拟接口(Node.js)
// server.js
const express = require('express');
const app = express();
const port = 3000;
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 简单验证逻辑
if (username === 'admin' && password === '123456') {
res.json({
code: 200,
message: '登录成功',
data: {
token: 'fake-token-123'
}
});
} else {
res.status(401).json({
code: 401,
message: '用户名或密码错误'
});
}
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});3. 运行效果
- 启动后端服务
- 启动前端开发服务器
- 在浏览器中访问登录页面
- 输入admin/123456登录
- 成功后会弹出"登录成功"提示,并保存token到localStorage
六、源码解析
1. 请求拦截器流程
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 1. 添加token到请求头
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
// 2. 设置Content-Type
config.headers['Content-Type'] = 'application/json';
// 3. 处理请求参数
if (config.method === 'post' && config.data) {
config.data = JSON.stringify(config.data);
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);- 优先处理token,避免重复代码
- 设置Content-Type确保服务器能正确解析
- 对post请求进行参数序列化,避免浏览器自动处理
2. 响应拦截器处理
service.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;
if (data.code === 200) {
return data.data;
}
return Promise.reject(data.message || '服务器响应异常');
},
(error: AxiosError) => {
if (error.response) {
console.error('响应错误:', error.response.status);
return Promise.reject(error.response.data || '服务器响应异常');
} else if (error.request) {
console.error('请求未收到响应:', error.request);
return Promise.reject('请求未收到响应');
} else {
console.error('请求设置错误:', error.message);
return Promise.reject('请求设置错误');
}
}
);- 响应成功时返回data.data,处理服务器返回的业务数据
- 响应失败时返回错误信息,统一处理错误提示
- 区分不同类型的错误:服务器响应错误、请求未收到响应、请求设置错误
七、进阶使用
1. 跨域请求处理
// 配置axios实例
const service: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 10000,
withCredentials: true, // 允许携带cookie
});在开发环境中,可以配置代理解决跨域问题:
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
},
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
});2. 请求缓存优化
// 使用axios-cache-adapter
import axios from 'axios';
import { CacheAdapter } from 'axios-cache-adapter';
const cacheAdapter = new CacheAdapter({
maxAge: 1000 * 60 * 10, // 10分钟
maxSize: 1000
});
const service: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 10000,
}).use(cacheAdapter);3. 并发请求处理
// 使用axios-concurrent库
import axios from 'axios';
import { concurrent } from 'axios-concurrent';
const service = axios.create({
baseURL: '/api',
timeout: 10000,
});
const concurrentService = concurrent(service, {
maxConcurrent: 5, // 最大并发数
maxQueue: 100 // 最大队列长度
});八、性能与工程实践
1. 性能优化策略
- 请求缓存:对不常变化的接口使用缓存,减少服务器压力
- 并发控制:使用axios-concurrent限制同时进行的请求数量
- 响应压缩:在服务器端启用Gzip压缩
- 减少请求次数:合并多个接口请求,避免多次往返
- 预加载策略:对高频访问的接口进行预加载
2. 异常处理规范
// 统一错误处理
export const handleRequestError = (error: string) => {
console.error('请求错误:', error);
alert(`请求出错: ${error}`);
// 记录错误日志
if (process.env.NODE_ENV === 'production') {
// 发送错误日志到服务器
// logger.error(error);
}
return Promise.reject(error);
};3. 安全风险分析
token安全:
- 使用HTTPS传输
- 设置secure和httpOnly标志
- 设置较短的token有效期
- 使用刷新token机制
CSRF防护:
- 在服务器端验证XSRF-TOKEN
- 使用withCredentials设置为true时需要处理
- 使用JWT替代传统session机制
数据安全:
- 使用HTTPS加密传输
- 对敏感数据进行加密处理
- 设置Content-Security-Policy头
九、常见问题与踩坑
1. 跨域问题
错误示例:
// 前端代码
axios.get('http://localhost:3000/api/user');解决方法:
- 使用vite的代理配置
- 使用CORS中间件
- 使用反向代理服务器
2. 拦截器顺序错误
错误示例:
service.interceptors.response.use(
(response) => { /* 响应拦截器 */ },
(error) => { /* 错误处理 */ }
);
service.interceptors.request.use(
(config) => { /* 请求拦截器 */ },
(error) => { /* 错误处理 */ }
);正确顺序:
// 先注册请求拦截器
service.interceptors.request.use(...);
// 再注册响应拦截器
service.interceptors.response.use(...);3. 类型定义不准确
错误示例:
// 响应拦截器中未处理错误码
if (data.code === 200) {
return data.data;
}改进方法:
// 增加类型声明
interface ResponseData<T> {
code: number;
message: string;
data: T;
}
// 响应拦截器处理
if (data.code === 200) {
return data.data;
}4. 错误处理不完整
错误示例:
try {
await post('/login', { username, password });
} catch (error) {
console.error(error);
}改进方法:
try {
await post('/login', { username, password });
} catch (error) {
handleRequestError(error as string);
}十、最佳实践
1. 推荐方案
- 统一的请求封装:所有API请求都通过封装后的request方法发起
- 类型安全:使用TypeScript定义接口和类型,确保类型安全
- 错误处理统一:所有错误都通过统一的handleRequestError处理
- 请求拦截器:统一处理token、Content-Type等通用配置
- 响应拦截器:统一处理成功/失败响应,返回标准化数据
2. 适用场景
- 中大型项目需要统一管理请求
- 需要统一的错误处理和提示
- 需要处理跨域、token、超时等通用需求
- 需要支持接口mock测试时
3. 不适用场景
- 非常小的项目,简单请求无需封装
- 需要高度定制化请求逻辑的特殊场景
- 需要实时性要求极高的场景
- 需要处理特殊格式数据(如二进制文件)的场景
十一、总结
本文深入探讨了vue3+vite+TS项目中axios的二次封装实现,从原理到实践,从基础到进阶,全面解析了其工作原理和使用方法。通过三个代码示例展示了拦截器配置、请求封装和错误处理的实现,提供了一个完整的登录案例演示了如何在实际项目中使用。
在实际开发中,二次封装可以显著提升开发效率和代码可维护性,但也要注意其适用场景。对于复杂项目,建议采用分模块、分功能的封装策略,结合接口管理工具,形成统一的请求规范。同时,要关注安全性、性能优化和错误处理,确保系统的稳定运行。
在开发过程中,需要注意常见的陷阱:如拦截器顺序、类型定义、错误处理等,这些都是容易犯的错误。通过本文的分析和示例,希望能够帮助开发者避免这些常见问题,写出更健壮、可维护的前端代码。
评论已关闭