'# 使用typescript封装axios
一、背景与问题
在现代前端开发中,HTTP请求是核心功能之一。Axios作为流行的HTTP客户端库,提供了丰富的功能,但其原生的使用方式存在以下问题:
- 类型安全不足:原生axios在TypeScript项目中需要手动定义接口,容易出现类型不匹配
- 重复代码多:每个请求都需要重复配置baseURL、headers等参数
- 错误处理不统一:不同接口的错误处理逻辑差异大
- 拦截器管理困难:缺乏统一的拦截器管理机制
- 性能优化不足:未考虑请求缓存、重试等机制
在大型项目中,这些问题会逐渐演变成维护成本和潜在的运行时错误。通过封装axios,可以构建统一的HTTP服务层,提升代码质量。
二、基本原理
Axios的封装本质上是构建一个统一的请求处理管道,包含以下核心组件:
- 类型定义:使用TypeScript的接口和泛型实现类型安全
- 请求拦截器:统一处理请求参数、token、loading状态等
- 响应拦截器:统一处理错误、数据转换、身份验证等
- 配置管理:集中管理baseURL、headers、超时等配置
- 错误处理机制:定义统一的错误类型和处理逻辑
Axios的底层使用Promise实现异步请求,通过拦截器队列处理请求和响应。每个拦截器都是一个函数,按顺序执行。
三、环境准备
# 创建项目结构
mkdir axios-encapsulation
cd axios-encapsulation
npm init -y
npm install axios typescript ts-node @types/axios
npx ts-node -p tsconfig.json// tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "."
},
"include": ["src"]
}四、核心实现
1. 基础封装
// src/http/index.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
// 定义通用响应类型
interface HttpResponse<T> {
code: number;
message: string;
data: T | null;
}
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig): AxiosRequestConfig => {
// 添加token
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error: any) => {
// 请求异常处理
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse): AxiosResponse<HttpResponse<any>> => {
// 响应数据转换
const { data } = response;
return {
...response,
data: {
code: data.code,
message: data.message,
data: data.data
}
};
},
(error: any): Promise<HttpResponse<any>> => {
// 响应错误处理
const { response } = error;
if (response) {
return Promise.resolve({
code: response.status,
message: response.statusText,
data: null
});
}
return Promise.resolve({
code: -1,
message: '网络异常',
data: null
});
}
);
export default service;关键代码解释:
- 创建axios实例时配置了基础URL和超时时间
- 请求拦截器中添加了token认证逻辑
- 响应拦截器统一处理了接口返回的数据结构
- 响应拦截器返回统一的HttpResponse类型
2. 请求封装
// src/http/request.ts
import service from './index';
// 定义请求方法
export async function get<T>(url: string, params?: any): Promise<T> {
try {
const response = await service.get(url, { params });
if (response.data.code === 200) {
return response.data.data;
}
throw new Error(response.data.message);
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
export async function post<T>(url: string, data?: any): Promise<T> {
try {
const response = await service.post(url, data);
if (response.data.code === 200) {
return response.data.data;
}
throw new Error(response.data.message);
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}3. 错误处理封装
// src/http/error.ts
export function handleHttpError(error: any): void {
if (error.response) {
// 响应错误(4xx/5xx)
console.error('服务器响应错误:', error.response.status);
} else if (error.request) {
// 无响应(网络问题)
console.error('无响应:', error.request);
} else {
// 设置请求错误
console.error('请求错误:', error.message);
}
}五、完整案例
项目结构
axios-encapsulation/
├── src/
│ ├── http/
│ │ ├── index.ts // axios封装
│ │ ├── request.ts // 请求方法
│ │ └── error.ts // 错误处理
│ └── main.ts // 入口文件
├── tsconfig.json
└── package.json使用示例
// src/main.ts
import { get, post } from './http/request';
async function main() {
try {
// 获取用户列表
const users = await get('/users', { page: 1, pageSize: 10 });
console.log('用户列表:', users);
// 创建新用户
const newUser = await post('/users', {
name: '张三',
email: 'zhangsan@example.com'
});
console.log('创建用户:', newUser);
} catch (error) {
handleHttpError(error);
}
}
main();六、源码解析
拦截器队列机制:
- Axios拦截器使用链式结构,每个拦截器返回的config或response会传递给下一个拦截器
- 请求拦截器在发送请求前执行,响应拦截器在接收到响应后执行
类型转换逻辑:
- 响应拦截器将原始响应数据转换为统一的HttpResponse类型
- 通过泛型参数
实现数据类型校验
错误处理机制:
- 区分了网络错误、服务器错误、客户端错误等不同场景
- 统一的错误处理函数可以集中处理日志、提示等逻辑
七、进阶使用
1. 请求重试机制
// src/http/retry.ts
import service from './index';
export async function retryGet<T>(url: string, params?: any, retries = 3): Promise<T> {
let attempt = 0;
while (attempt < retries) {
try {
const response = await service.get(url, { params });
if (response.data.code === 200) {
return response.data.data;
}
throw new Error(response.data.message);
} catch (error) {
console.warn(`尝试 ${attempt + 1} 失败: ${error.message}`);
attempt++;
if (attempt < retries) {
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
throw new Error('请求重试失败');
}2. 请求缓存机制
// src/http/cache.ts
import service from './index';
type CacheConfig = {
maxAge?: number; // 缓存最大时间(秒)
cacheKey?: (url: string, params?: any) => string;
};
export async function cachedGet<T>(url: string, params?: any, config?: CacheConfig): Promise<T> {
const cacheKey = config?.cacheKey?.(url, params) || `${url}?${new URLSearchParams(params).toString()}`;
// 检查缓存
const cached = localStorage.getItem(cacheKey);
if (cached) {
const { timestamp, data } = JSON.parse(cached);
if (Date.now() - timestamp < (config?.maxAge || 3600) * 1000) {
return data;
}
}
// 执行请求
const result = await get(url, params);
// 存储缓存
localStorage.setItem(cacheKey, JSON.stringify({
timestamp: Date.now(),
data: result
}));
return result;
}八、性能与工程实践
1. 性能优化
- 减少拦截器数量:避免不必要的中间处理
- 使用缓存:对不常变化的数据进行缓存
- 限制并发请求:使用axios的
concurrency参数控制并发数量 - 压缩请求体:对大数据量请求进行压缩处理
2. 异常处理
- 网络错误重试:对网络不稳定场景进行重试
- 超时处理:设置合理的超时时间
- 错误日志记录:记录详细的错误信息用于后续分析
3. 安全考虑
- HTTPS强制:确保所有请求使用HTTPS
- CORS配置:合理配置CORS策略,避免安全漏洞
- 敏感数据加密:对敏感数据进行加密传输
- 防止CSRF:添加CSRF防护机制
九、常见问题与踩坑
1. 类型定义错误
// 错误示例
const response = await service.get('/users');
console.log(response.data.name); // 编译错误问题:未定义具体类型,导致类型检查失效
解决:使用泛型明确类型
const response = await get('/users', { page: 1 }); // 明确类型
console.log(response.name);2. 拦截器顺序问题
// 错误示例
service.interceptors.request.use((config) => {
// 拦截器1
return config;
});
service.interceptors.request.use((config) => {
// 拦截器2
return config;
});问题:拦截器顺序错误导致配置覆盖
解决:使用use方法添加拦截器
3. 缓存失效问题
// 错误示例
const data = await cachedGet('/users', { page: 1 });
console.log(data);问题:缓存键未正确生成
解决:确保cacheKey函数返回唯一标识
十、最佳实践
- 统一的错误处理:所有请求都应该经过统一的错误处理流程
- 类型安全:使用泛型和接口确保类型安全
- 拦截器分层:将公共逻辑放在拦截器中,避免重复代码
- 配置集中管理:将baseURL、headers等配置集中管理
- 接口文档化:为每个接口定义清晰的接口文档
- 性能监控:添加请求耗时监控,优化慢接口
- 安全防护:添加必要的安全防护措施
十一、总结
通过TypeScript封装axios,我们构建了一个统一的HTTP服务层,解决了原始axios在类型安全、代码重复、错误处理等方面的痛点。这种封装方式特别适合大型项目,能够显著提升代码质量和可维护性。
什么时候应该使用:
- 项目规模较大,需要统一的HTTP服务层
- 需要严格的类型安全保证
- 有统一的错误处理和日志记录需求
- 需要添加统一的拦截器逻辑(如token认证、请求日志等)
什么时候不应该使用:
- 极小的项目,增加封装成本不划算
- 需要高度定制的请求处理逻辑
- 临时性的接口调用需求
通过合理的封装和实践,可以显著提升开发效率和代码质量,同时为后续的维护和扩展提供良好的基础。在实际开发中,需要根据项目需求灵活调整封装策略,找到最适合的平衡点。