1. 为什么选择Axios作为前端请求库
在2023年的前端开发中,数据请求仍然是每个项目必不可少的部分。虽然浏览器原生提供了fetch API,但大多数专业项目仍然选择Axios作为HTTP客户端,这背后有几个关键原因:
首先,Axios提供了更完善的错误处理机制。当使用fetch时,即使服务器返回4xx或5xx状态码,fetch的Promise也会resolve,开发者需要手动检查response.ok。而Axios会自动将非2xx状态码视为错误,大大简化了错误处理逻辑。例如:
javascript复制// fetch的错误处理需要额外判断
fetch('/api/data')
.then(response => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.catch(error => console.error(error));
// Axios的错误处理更直观
axios.get('/api/data')
.catch(error => console.error(error));
其次,Axios内置了请求/响应拦截器机制。这个功能在实际项目中极为实用,可以统一处理以下场景:
- 为所有请求自动添加Authorization头
- 统一处理API返回的错误码
- 在请求发出前显示loading状态,响应返回后隐藏
- 对响应数据进行预处理
typescript复制// 添加请求拦截器
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 添加响应拦截器
axios.interceptors.response.use(
response => response.data,
error => {
if (error.response.status === 401) {
router.push('/login');
}
return Promise.reject(error);
}
);
第三,Axios在TypeScript中的支持非常完善。从1.0版本开始,Axios就内置了TypeScript类型定义,这使得在TS项目中使用Axios能获得完整的类型提示和检查。我们可以精确地定义请求参数和响应数据的类型:
typescript复制interface User {
id: number;
name: string;
email: string;
}
// 明确指定响应数据类型
axios.get<User[]>('/api/users')
.then(response => {
const users = response.data; // users的类型自动推断为User[]
});
此外,Axios还提供了一些实用功能:
- 自动转换JSON数据
- 支持请求取消
- 客户端XSRF防护
- 上传进度监控
- 同时支持浏览器和Node.js环境
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Axios核心API快速掌握
2.1 基础请求方法
Axios提供了对应HTTP方法的快捷API,最常用的包括:
typescript复制// GET请求 - 获取数据
axios.get('/api/users', {
params: { page: 1, limit: 10 } // 查询参数
});
// POST请求 - 创建资源
axios.post('/api/users', {
name: 'John',
email: 'john@example.com'
});
// PUT请求 - 更新整个资源
axios.put('/api/users/1', {
name: 'John Updated',
email: 'john.updated@example.com'
});
// PATCH请求 - 部分更新资源
axios.patch('/api/users/1', {
email: 'new.email@example.com'
});
// DELETE请求 - 删除资源
axios.delete('/api/users/1');
2.2 请求配置详解
Axios允许通过配置对象来自定义请求行为,常用的配置项包括:
typescript复制{
url: '/api/users', // 请求地址
method: 'get', // 请求方法
baseURL: 'https://api.example.com', // 基础URL
headers: { 'X-Custom-Header': 'value' }, // 自定义头
params: { id: 1 }, // URL参数
data: { name: 'John' }, // 请求体数据
timeout: 1000, // 超时时间(ms)
responseType: 'json', // 响应格式(json/text/blob等)
withCredentials: true, // 是否携带凭据(cookie等)
auth: { // HTTP基本认证
username: 'user',
password: 'pass'
},
// 其他配置...
}
2.3 创建Axios实例
在实际项目中,我们通常会创建自定义的Axios实例,而不是直接使用全局的axios对象:
typescript复制import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com/v1',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// 使用自定义实例
api.get('/users').then(response => {
// 处理响应
});
这种方式的好处是可以为不同的API端点创建不同的配置实例,避免配置冲突。
3. Vue3+TS中的Axios最佳实践
3.1 在Vue3项目中封装Axios
在Vue3项目中,我们通常会创建一个专门的request.ts文件来封装Axios:
typescript复制// src/utils/request.ts
import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
class Http {
private instance: AxiosInstance;
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
this.setupInterceptors();
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => response.data,
(error) => {
if (error.response?.status === 401) {
// 处理未授权错误
}
return Promise.reject(error);
}
);
}
public request<T>(config: AxiosRequestConfig): Promise<T> {
return this.instance.request(config);
}
public get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.request({ ...config, method: 'get', url });
}
public post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.request({ ...config, method: 'post', url, data });
}
// 其他方法...
}
export const http = new Http({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
});
3.2 在组件中使用封装的Axios
在Vue组件中,我们可以这样使用封装好的http实例:
typescript复制<script setup lang="ts">
import { ref } from 'vue';
import { http } from '@/utils/request';
interface User {
id: number;
name: string;
email: string;
}
const users = ref<User[]>([]);
const loading = ref(false);
const fetchUsers = async () => {
try {
loading.value = true;
users.value = await http.get<User[]>('/users');
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
loading.value = false;
}
};
fetchUsers();
</script>
<template>
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</template>
3.3 结合Composition API使用
在Vue3的Composition API中,我们可以进一步封装可复用的请求逻辑:
typescript复制// src/composables/useApi.ts
import { ref } from 'vue';
import { http } from '@/utils/request';
export function useApi<T>(url: string) {
const data = ref<T>();
const error = ref<Error>();
const loading = ref(false);
const execute = async (config?: AxiosRequestConfig) => {
try {
loading.value = true;
error.value = undefined;
data.value = await http.get<T>(url, config);
} catch (err) {
error.value = err as Error;
} finally {
loading.value = false;
}
};
return {
data,
error,
loading,
execute
};
}
然后在组件中使用:
typescript复制<script setup lang="ts">
import { useApi } from '@/composables/useApi';
interface Product {
id: number;
name: string;
price: number;
}
const { data: products, loading, error, execute: fetchProducts } = useApi<Product[]>('/products');
// 可以传递额外配置
const fetchFeaturedProducts = () => {
fetchProducts({ params: { featured: true } });
};
</script>
4. 实战案例:用户管理系统API集成
4.1 用户登录实现
typescript复制// src/api/auth.ts
import { http } from '@/utils/request';
interface LoginParams {
email: string;
password: string;
}
interface LoginResponse {
token: string;
user: {
id: number;
name: string;
email: string;
};
}
export const login = (data: LoginParams) => {
return http.post<LoginResponse>('/auth/login', data);
};
// 在组件中使用
<script setup lang="ts">
import { ref } from 'vue';
import { login } from '@/api/auth';
const email = ref('');
const password = ref('');
const loading = ref(false);
const handleLogin = async () => {
try {
loading.value = true;
const { token, user } = await login({
email: email.value,
password: password.value
});
localStorage.setItem('token', token);
// 跳转到首页...
} catch (error) {
alert('Login failed');
} finally {
loading.value = false;
}
};
</script>
4.2 分页获取用户列表
typescript复制// src/api/user.ts
import { http } from '@/utils/request';
interface PaginationParams {
page: number;
limit: number;
}
interface PaginationResult<T> {
items: T[];
total: number;
page: number;
limit: number;
}
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
export const getUsers = (params: PaginationParams) => {
return http.get<PaginationResult<User>>('/users', { params });
};
// 在组件中使用
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getUsers } from '@/api/user';
const users = ref<User[]>([]);
const total = ref(0);
const page = ref(1);
const limit = ref(10);
const loading = ref(false);
const fetchUsers = async () => {
try {
loading.value = true;
const result = await getUsers({
page: page.value,
limit: limit.value
});
users.value = result.items;
total.value = result.total;
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
loading.value = false;
}
};
onMounted(fetchUsers);
</script>
4.3 文件上传实现
typescript复制// src/api/upload.ts
import { http } from '@/utils/request';
export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
const formData = new FormData();
formData.append('file', file);
return http.post<{ url: string }>('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percent);
}
}
});
};
// 在组件中使用
<script setup lang="ts">
import { ref } from 'vue';
import { uploadFile } from '@/api/upload';
const file = ref<File | null>(null);
const progress = ref(0);
const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle');
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement;
if (target.files) {
file.value = target.files[0];
}
};
const handleUpload = async () => {
if (!file.value) return;
try {
uploadStatus.value = 'uploading';
const { url } = await uploadFile(file.value, (p) => {
progress.value = p;
});
console.log('File uploaded:', url);
uploadStatus.value = 'success';
} catch (error) {
console.error('Upload failed:', error);
uploadStatus.value = 'error';
}
};
</script>
5. 高级技巧与性能优化
5.1 请求取消
在某些场景下,我们需要取消正在进行的请求(如组件卸载时或用户快速切换页面时):
typescript复制// 使用AbortController取消请求
const controller = new AbortController();
axios.get('/api/data', {
signal: controller.signal
}).catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
// 处理其他错误
}
});
// 取消请求
controller.abort('Operation canceled by the user.');
// 在Vue组件中使用
<script setup lang="ts">
import { onUnmounted, ref } from 'vue';
import axios from 'axios';
const data = ref();
const controller = ref<AbortController>();
const fetchData = async () => {
controller.value = new AbortController();
try {
const response = await axios.get('/api/data', {
signal: controller.value.signal
});
data.value = response.data;
} catch (error) {
if (!axios.isCancel(error)) {
console.error('Error fetching data:', error);
}
}
};
onUnmounted(() => {
controller.value?.abort();
});
</script>
5.2 请求重试机制
对于某些临时性错误(如网络波动),我们可以实现自动重试机制:
typescript复制// 封装带重试功能的请求
async function requestWithRetry(
config: AxiosRequestConfig,
maxRetries = 3,
retryDelay = 1000
) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await axios(config);
return response.data;
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
}
throw lastError;
}
// 使用示例
requestWithRetry({
method: 'get',
url: '/api/data'
}).then(data => {
console.log('Data:', data);
}).catch(error => {
console.error('Request failed after retries:', error);
});
5.3 并发请求优化
当需要同时发送多个请求时,可以使用axios.all和axios.spread:
typescript复制// 同时发送多个请求
const [users, posts] = await Promise.all([
axios.get('/api/users'),
axios.get('/api/posts')
]);
// 或者使用axios.all(效果与Promise.all相同)
axios.all([
axios.get('/api/users'),
axios.get('/api/posts')
]).then(axios.spread((usersResponse, postsResponse) => {
console.log('Users:', usersResponse.data);
console.log('Posts:', postsResponse.data);
}));
5.4 性能监控与统计
我们可以通过拦截器实现请求性能监控:
typescript复制axios.interceptors.request.use(config => {
config.metadata = { startTime: performance.now() };
return config;
});
axios.interceptors.response.use(response => {
const duration = performance.now() - response.config.metadata.startTime;
console.log(`Request to ${response.config.url} took ${duration.toFixed(2)}ms`);
return response;
}, error => {
if (error.config) {
const duration = performance.now() - error.config.metadata.startTime;
console.error(`Request to ${error.config.url} failed after ${duration.toFixed(2)}ms`);
}
return Promise.reject(error);
});
6. 常见问题与解决方案
6.1 CORS跨域问题
当遇到跨域问题时,需要确保:
-
后端正确配置了CORS头:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
-
前端Axios配置:
typescript复制axios.get('https://api.example.com/data', {
withCredentials: true // 如果需要发送cookie
});
6.2 处理CSRF防护
对于CSRF防护,Axios提供了两种解决方案:
- 自动从cookie读取XSRF-TOKEN并设置为X-XSRF-TOKEN头:
typescript复制axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
- 手动添加CSRF令牌:
typescript复制const csrfToken = getCookie('csrf_token');
axios.post('/api/data', data, {
headers: {
'X-CSRF-TOKEN': csrfToken
}
});
6.3 处理大文件下载
对于大文件下载,可以使用blob响应类型和URL.createObjectURL:
typescript复制axios.get('/api/download', {
responseType: 'blob'
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
6.4 类型扩展与自定义配置
我们可以扩展Axios的类型定义以适应项目需求:
typescript复制// 扩展AxiosRequestConfig
declare module 'axios' {
interface AxiosRequestConfig {
showLoading?: boolean;
retryTimes?: number;
// 其他自定义配置...
}
}
// 使用自定义配置
axios.interceptors.request.use(config => {
if (config.showLoading) {
// 显示loading
}
return config;
});
axios.get('/api/data', {
showLoading: true,
retryTimes: 3
});
7. 测试与调试技巧
7.1 Mock请求数据
在开发阶段,我们可以使用axios-mock-adapter来模拟API:
typescript复制import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
// 模拟GET请求
mock.onGet('/users').reply(200, {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
});
// 模拟带参数的GET请求
mock.onGet('/users', { params: { id: 1 } }).reply(200, {
user: { id: 1, name: 'John' }
});
// 模拟POST请求
mock.onPost('/login').reply(config => {
const { email, password } = JSON.parse(config.data);
if (email === 'test@example.com' && password === 'password') {
return [200, { token: 'fake-token' }];
}
return [401, { message: 'Invalid credentials' }];
});
7.2 使用Postman调试
虽然Axios是前端库,但使用Postman调试API接口非常有用:
-
在Postman中构建与Axios相同的请求:
- 相同的URL、方法、头、参数
- 相同的请求体数据
-
比较Postman和Axios的响应:
- 如果Postman工作正常但Axios失败,通常是前端配置问题
- 如果两者都失败,通常是后端API问题
7.3 浏览器开发者工具调试
在Chrome开发者工具中:
-
Network面板:
- 查看请求是否发出
- 检查请求头、参数是否正确
- 查看响应状态码和数据
-
Console面板:
- 捕获并检查错误信息
- 使用console.log调试请求流程
-
使用断点调试拦截器:
- 在axios拦截器中设置断点
- 逐步执行查看请求/响应的变化
7.4 单元测试Axios
使用jest测试Axios相关代码:
typescript复制import axios from 'axios';
import { login } from '@/api/auth';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('auth API', () => {
it('should login successfully', async () => {
const mockResponse = { data: { token: 'fake-token' } };
mockedAxios.post.mockResolvedValue(mockResponse);
const result = await login({
email: 'test@example.com',
password: 'password'
});
expect(result.token).toBe('fake-token');
expect(mockedAxios.post).toHaveBeenCalledWith(
'/auth/login',
{
email: 'test@example.com',
password: 'password'
}
);
});
});
