在前后端分离项目中,前端最常见的工作之一就是向后端发送 HTTP 请求。
例如:
1 2 3 4 5 6 7 8 9
| Vue / React ↓ Axios ↓ HTTP Request ↓ FastAPI / Spring Boot / Express ↓ Database
|
浏览器本身提供了 fetch,但在实际 Vue、React 项目中,Axios 依然很常见,因为它提供了比较完整的请求配置、拦截器、超时控制、请求实例、统一错误处理等能力。
本文从最基础的 Axios 请求讲到项目中常用的 Axios 二次封装。
一、Axios 基础入门
1.1 Axios 是什么
Axios 是一个基于 Promise 的 HTTP 客户端,可以运行在浏览器和 Node.js 环境中。
简单来说:
1
| Axios = 帮助 JavaScript 向服务器发送 HTTP 请求的工具
|
例如前端请求后端用户数据:
1 2 3 4 5
| import axios from 'axios'
const response = await axios.get('/api/users')
console.log(response.data)
|
假设后端返回:
1 2 3 4 5 6 7 8 9 10
| { "code": 200, "message": "success", "data": [ { "id": 1, "name": "Tom" } ] }
|
那么:
就是服务器返回的数据。
Axios 最常见的使用场景包括:
- GET 查询数据
- POST 提交数据
- PUT 修改数据
- DELETE 删除数据
- Token 身份认证
- 文件上传
- 文件下载
- 请求拦截
- 响应拦截
- 请求错误统一处理
1.2 安装 Axios
Vue、React、Vite 等现代前端项目通常直接通过 npm、pnpm 或 yarn 安装。
npm
pnpm
yarn
安装完成后:
1
| import axios from 'axios'
|
即可使用。
1.3 第一个 Axios 请求
最简单的 GET 请求:
1 2 3 4 5 6 7 8 9
| import axios from 'axios'
axios.get('https://example.com/api/users') .then(response => { console.log(response.data) }) .catch(error => { console.error(error) })
|
Axios 返回的是一个 Promise,因此也可以使用 async/await。
实际项目中更加推荐:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import axios from 'axios'
async function getUsers() { try { const response = await axios.get( 'https://example.com/api/users' )
console.log(response.data) } catch (error) { console.error(error) } }
getUsers()
|
相比 .then():
1 2 3
| axios.get('/api/users') .then(...) .catch(...)
|
async/await 在业务逻辑比较复杂的时候通常更加容易阅读。
1.4 Axios 返回的数据是什么
Axios 请求成功之后,并不是直接返回后端的数据,而是返回一个 response 对象。
例如:
1 2 3
| const response = await axios.get('/api/users')
console.log(response)
|
response 常见结构:
1 2 3 4 5 6 7 8
| { data: {}, status: 200, statusText: 'OK', headers: {}, config: {}, request: {} }
|
最常使用的是:
例如:
1 2 3 4 5
| const response = await axios.get('/api/users')
const users = response.data
console.log(users)
|
如果后端统一返回:
1 2 3 4 5 6 7
| { "code": 200, "message": "success", "data": { "name": "Tom" } }
|
那么真正需要的业务数据可能是:
也就是:
1
| const user = response.data.data
|
这也是为什么真实项目中通常会使用响应拦截器:
1 2 3 4 5
| response ↓ response.data ↓ 业务代码
|
把多余的一层提前处理掉。
二、HTTP 请求基础
2.1 发送 GET 请求
GET 通常用于查询数据。
最简单的方式:
1
| const response = await axios.get('/api/users')
|
如果需要携带查询参数:
1 2 3 4 5 6
| const response = await axios.get('/api/users', { params: { page: 1, pageSize: 10 } })
|
最终发送:
1
| GET /api/users?page=1&pageSize=10
|
例如:
1 2 3 4 5 6 7 8 9 10 11
| async function getUsers() { const response = await axios.get('/api/users', { params: { keyword: 'Tom', page: 1, pageSize: 10 } })
console.log(response.data) }
|
最终 URL 类似:
1
| /api/users?keyword=Tom&page=1&pageSize=10
|
因此 GET 请求参数通常放在:
里面。
2.2 发送 POST 请求
POST 一般用于创建资源或者提交数据。
例如用户登录:
1 2 3 4
| const response = await axios.post('/api/login', { username: 'admin', password: '123456' })
|
这里第二个参数:
1 2 3 4
| { username: 'admin', password: '123456' }
|
就是请求体。
等价于:
1 2 3 4 5 6
| POST /api/login
{ "username": "admin", "password": "123456" }
|
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12
| async function login() { try { const response = await axios.post('/api/login', { username: 'admin', password: '123456' })
console.log(response.data) } catch (error) { console.error(error) } }
|
2.3 PUT 请求
PUT 通常用于修改资源。
例如修改用户:
1 2 3 4
| await axios.put('/api/users/1', { name: 'Jerry', age: 20 })
|
发送:
请求体:
1 2 3 4
| { "name": "Jerry", "age": 20 }
|
2.4 PATCH 请求
PATCH 同样用于修改资源。
例如:
1 2 3
| await axios.patch('/api/users/1', { name: 'Jerry' })
|
PUT 和 PATCH 在 RESTful API 中通常存在语义上的区别。
PUT 更倾向于:
PATCH 更倾向于:
例如原始用户:
1 2 3 4 5
| { "name": "Tom", "age": 18, "email": "tom@example.com" }
|
只修改用户名:
1 2 3
| await axios.patch('/api/users/1', { name: 'Jerry' })
|
2.5 DELETE 请求
删除用户:
1
| await axios.delete('/api/users/1')
|
对应:
如果需要传递查询参数:
1 2 3 4 5
| await axios.delete('/api/users/1', { params: { force: true } })
|
最终:
1
| DELETE /api/users/1?force=true
|
2.6 常见请求方式对比
| 方法 |
作用 |
Axios |
| GET |
查询 |
axios.get() |
| POST |
创建 |
axios.post() |
| PUT |
完整更新 |
axios.put() |
| PATCH |
部分更新 |
axios.patch() |
| DELETE |
删除 |
axios.delete() |
最常见的 CRUD 对应关系:
1 2 3 4 5 6 7
| Create → POST
Read → GET
Update → PUT / PATCH
Delete → DELETE
|
2.7 Axios 通用配置方式
除了:
还可以直接传入完整配置对象:
1 2 3 4 5 6 7
| const response = await axios({ method: 'get', url: '/api/users', params: { page: 1 } })
|
POST:
1 2 3 4 5 6 7 8
| const response = await axios({ method: 'post', url: '/api/users', data: { name: 'Tom', age: 20 } })
|
常见配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| axios({ method: 'post',
url: '/api/users',
baseURL: 'https://api.example.com',
params: { page: 1 },
data: { name: 'Tom' },
headers: { Authorization: 'Bearer token' },
timeout: 5000 })
|
其中需要重点区分:
和:
params 一般表示 URL 查询参数:
data 一般表示 HTTP Request Body。
某些接口需要指定请求头。
例如:
1 2 3 4 5
| await axios.get('/api/users', { headers: { Authorization: 'Bearer abc123' } })
|
发送的请求头:
1
| Authorization: Bearer abc123
|
POST:
1 2 3 4 5 6 7 8 9 10 11
| await axios.post( '/api/users', { name: 'Tom' }, { headers: { Authorization: 'Bearer abc123' } } )
|
注意 Axios POST 的参数顺序:
1 2 3 4 5
| axios.post( url, data, config )
|
也就是:
1 2 3 4 5
| 第一个参数:URL
第二个参数:请求数据
第三个参数:请求配置
|
三、身份认证与拦截器
3.1 Token 身份认证
现在很多前后端分离项目都会使用 JWT。
用户登录后后端返回:
前端保存:
1
| localStorage.setItem('token', response.data.token)
|
请求其他接口时:
1 2 3 4 5 6 7
| const token = localStorage.getItem('token')
await axios.get('/api/user/profile', { headers: { Authorization: `Bearer ${token}` } })
|
但是问题来了。
如果项目里面有 100 个 API:
1 2 3 4
| axios.get(...) axios.post(...) axios.put(...) axios.delete(...)
|
难道每次都手动写:
1
| Authorization: `Bearer ${token}`
|
当然不会。这种情况交给请求拦截器处理。
3.2 请求拦截器
请求拦截器可以在 HTTP 请求真正发送之前执行代码。
流程:
1 2 3 4 5 6 7 8 9
| 调用 API ↓ 请求拦截器 ↓ 添加 Token ↓ 发送 HTTP 请求 ↓ 后端服务器
|
例如:
1 2 3 4 5 6 7 8 9 10
| axios.interceptors.request.use( config => { console.log('准备发送请求')
return config }, error => { return Promise.reject(error) } )
|
注意一定要:
如果不返回,请求就无法正常继续。
3.3 请求拦截器自动携带 Token
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| axios.interceptors.request.use( config => { const token = localStorage.getItem('token')
if (token) { config.headers.Authorization = `Bearer ${token}` }
return config }, error => { return Promise.reject(error) } )
|
之后业务代码只需要:
Axios 会自动变成:
1 2 3
| GET /api/users
Authorization: Bearer xxxxx
|
无需每个接口重复添加 Token。
3.4 响应拦截器
响应拦截器与请求拦截器相反。
它会在:
之后执行。
流程:
1 2 3 4 5 6 7
| 后端服务器 ↓ HTTP Response ↓ 响应拦截器 ↓ 业务代码
|
例如:
1 2 3 4 5 6 7 8 9 10 11 12
| axios.interceptors.response.use( response => { console.log('请求成功')
return response }, error => { console.error('请求失败')
return Promise.reject(error) } )
|
3.5 为什么实际项目要使用响应拦截器
假设每个接口都返回:
1 2 3 4 5 6 7
| { "code": 200, "message": "success", "data": { "name": "Tom" } }
|
正常 Axios:
1 2 3
| const response = await axios.get('/api/user')
console.log(response.data.data)
|
每次都写:
非常麻烦。
所以可以:
1 2 3 4 5
| axios.interceptors.response.use( response => { return response.data } )
|
这时候:
1 2 3
| const response = await axios.get('/api/user')
console.log(response)
|
得到的就是:
1 2 3 4 5 6 7
| { "code": 200, "message": "success", "data": { "name": "Tom" } }
|
还可以继续处理:
1 2 3 4 5
| axios.interceptors.response.use( response => { return response.data.data } )
|
这时业务代码:
1 2 3
| const user = await axios.get('/api/user')
console.log(user.name)
|
会更加简洁。
不过真实项目中是否直接返回 data.data,要根据后端统一响应格式决定。
四、Axios 工程化封装
4.1 创建 Axios 实例
真实项目一般不建议直接修改全局 axios。
更加推荐:
创建属于当前项目的 Axios 实例。
例如:
1 2 3 4 5 6
| import axios from 'axios'
const request = axios.create({ baseURL: 'http://localhost:8000/api', timeout: 10000 })
|
之后:
真正发送:
1
| http://localhost:8000/api/users
|
这里:
可以理解为公共 API 前缀。
4.2 baseURL 的作用
假设后端地址:
接口分别为:
1 2 3 4 5 6 7
| http://localhost:8000/api/users
http://localhost:8000/api/login
http://localhost:8000/api/articles
http://localhost:8000/api/comments
|
如果不用 baseURL:
1 2 3 4 5
| axios.get('http://localhost:8000/api/users')
axios.post('http://localhost:8000/api/login')
axios.get('http://localhost:8000/api/articles')
|
大量重复。
设置:
1 2 3
| const request = axios.create({ baseURL: 'http://localhost:8000/api' })
|
之后:
1 2 3 4 5
| request.get('/users')
request.post('/login')
request.get('/articles')
|
代码会清晰很多。
4.3 timeout 请求超时
可以设置:
1 2 3
| const request = axios.create({ timeout: 10000 })
|
表示:
如果请求超过指定时间仍未完成,Axios 会终止请求并抛出错误。
也可以单独设置:
1 2 3
| axios.get('/api/users', { timeout: 5000 })
|
表示最多等待 5 秒。
4.4 项目中常见 Axios 封装
一般项目目录会类似:
1 2 3 4 5 6 7 8 9 10 11 12
| src ├── api │ ├── user.ts │ ├── article.ts │ └── auth.ts │ ├── utils │ └── request.ts │ ├── views ├── components └── main.ts
|
其中:
负责 Axios 配置。
负责具体接口。
4.5 封装 request.ts
JavaScript 项目可以写:
1 2 3 4 5 6 7 8
| // src/utils/request.js
import axios from 'axios'
const request = axios.create({ baseURL: '/api', timeout: 10000 })
|
添加请求拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| request.interceptors.request.use( config => { const token = localStorage.getItem('token')
if (token) { config.headers.Authorization = `Bearer ${token}` }
return config }, error => { return Promise.reject(error) } )
|
添加响应拦截器:
1 2 3 4 5 6 7 8
| request.interceptors.response.use( response => { return response.data }, error => { return Promise.reject(error) } )
|
最终:
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import axios from 'axios'
const request = axios.create({ baseURL: '/api', timeout: 10000 })
request.interceptors.request.use( config => { const token = localStorage.getItem('token')
if (token) { config.headers.Authorization = `Bearer ${token}` }
return config }, error => { return Promise.reject(error) } )
request.interceptors.response.use( response => { return response.data }, error => { return Promise.reject(error) } )
export default request
|
这基本就是 Axios 封装的最小版本。
4.6 统一处理 HTTP 错误
Axios 请求失败后可以通过:
获取服务器返回的信息。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| request.interceptors.response.use( response => { return response.data },
error => { if (error.response) { console.log(error.response.status) console.log(error.response.data) }
return Promise.reject(error) } )
|
HTTP 状态码常见情况:
| 状态码 |
含义 |
| 200 |
请求成功 |
| 201 |
创建成功 |
| 400 |
请求参数错误 |
| 401 |
未登录或 Token 失效 |
| 403 |
没有权限 |
| 404 |
资源不存在 |
| 500 |
服务器错误 |
| 502 |
网关错误 |
| 503 |
服务暂时不可用 |
例如统一处理 401:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| request.interceptors.response.use( response => { return response.data },
error => { if (error.response?.status === 401) { localStorage.removeItem('token')
window.location.href = '/login' }
return Promise.reject(error) } )
|
这样 Token 失效之后,就可以自动退出登录。
4.7 区分不同类型的错误
一个 Axios 请求失败,不一定意味着后端报错。
一般可以分为三种情况。
第一种:
存在。
说明服务器已经响应,但是状态码不是 Axios 认为的成功状态。
例如:
第二种:
存在,但是:
不存在。
说明请求已经发送出去,但是没有收到服务器响应。
可能的原因包括:
1 2 3 4 5 6 7 8 9
| 网络断开
服务器没有启动
请求超时
跨域配置问题
网关故障
|
第三种情况:
请求本身配置过程中就出现了问题。
因此可以写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| request.interceptors.response.use( response => response.data,
error => { if (error.response) { console.error( '服务器返回错误:', error.response.status ) } else if (error.request) { console.error('服务器没有响应') } else { console.error( '请求配置错误:', error.message ) }
return Promise.reject(error) } )
|
4.8 业务状态码处理
实际项目中经常出现:
HTTP 状态:
但是业务状态:
1 2 3 4 5
| { "code": 40001, "message": "用户名不存在", "data": null }
|
这时候不能只判断 HTTP 状态码。
可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| request.interceptors.response.use( response => { const result = response.data
if (result.code !== 200) { console.error(result.message)
return Promise.reject( new Error(result.message) ) }
return result.data },
error => { return Promise.reject(error) } )
|
这样业务层只会收到真正的数据:
1 2 3
| const user = await getUser()
console.log(user)
|
而不是:
1 2 3 4 5
| { code: 200, message: 'success', data: user }
|
不过这部分一定要根据你自己的后端接口协议进行调整。
4.9 封装 API
有了:
之后,不建议在 Vue 组件中到处写:
更加推荐继续封装 API。
例如:
代码:
1 2 3 4 5 6 7 8 9
| import request from '@/utils/request'
export function getUsers(params) { return request({ url: '/users', method: 'get', params }) }
|
新增用户:
1 2 3 4 5 6 7
| export function createUser(data) { return request({ url: '/users', method: 'post', data }) }
|
修改用户:
1 2 3 4 5 6 7
| export function updateUser(id, data) { return request({ url: `/users/${id}`, method: 'put', data }) }
|
删除用户:
1 2 3 4 5 6
| export function deleteUser(id) { return request({ url: `/users/${id}`, method: 'delete' }) }
|
最终:
1 2 3 4 5 6
| import { getUsers, createUser, updateUser, deleteUser } from '@/api/user'
|
业务代码就不需要关注底层 HTTP 请求细节。
4.10 完整 CRUD API 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| // src/api/user.js
import request from '@/utils/request'
export function getUsers(params) { return request({ url: '/users', method: 'get', params }) }
export function getUser(id) { return request({ url: `/users/${id}`, method: 'get' }) }
export function createUser(data) { return request({ url: '/users', method: 'post', data }) }
export function updateUser(id, data) { return request({ url: `/users/${id}`, method: 'put', data }) }
export function deleteUser(id) { return request({ url: `/users/${id}`, method: 'delete' }) }
|
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { getUsers, createUser } from '@/api/user'
async function loadUsers() { const data = await getUsers({ page: 1, pageSize: 10 })
console.log(data) }
|
整个请求链路就变成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Vue Component
↓
user.js
↓
request.js
↓
Axios
↓
HTTP
↓
Backend
|
这是实际项目中比较常见的 Axios 分层方式。
4.11 Axios + Vue 3 示例
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <script setup> import { ref, onMounted } from 'vue' import { getUsers } from '@/api/user'
const users = ref([]) const loading = ref(false)
async function loadUsers() { loading.value = true
try { users.value = await getUsers({ page: 1, pageSize: 10 }) } catch (error) { console.error('获取用户失败', error) } finally { loading.value = false } }
onMounted(() => { loadUsers() }) </script>
<template> <div> <div v-if="loading"> 加载中... </div>
<div v-for="user in users" :key="user.id" > {{ user.name }} </div> </div> </template>
|
这是一个典型的 loading 处理流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| loading = true
↓
发送 Axios 请求
↓
成功 / 失败
↓
finally
↓
loading = false
|
五、Axios 与 TypeScript
5.1 Axios + TypeScript
Axios 本身已经包含 TypeScript 类型定义,因此 TS 项目可以直接使用。
假设后端返回:
1 2 3 4 5
| { "id": 1, "name": "Tom", "email": "tom@example.com" }
|
定义:
1 2 3 4 5
| interface User { id: number name: string email: string }
|
请求:
1 2 3 4 5 6 7
| import axios from 'axios'
const response = await axios.get<User>( '/api/users/1' )
console.log(response.data.name)
|
这时:
会被 TypeScript 推断为:
从而获得代码提示。
5.2 统一后端响应类型
假设后端所有接口统一:
1 2 3 4 5
| { "code": 200, "message": "success", "data": {} }
|
可以定义:
1 2 3 4 5
| interface ApiResponse<T> { code: number message: string data: T }
|
用户:
1 2 3 4
| interface User { id: number name: string }
|
请求:
1 2 3
| const response = await axios.get< ApiResponse<User> >('/api/users/1')
|
此时:
类型就是:
这是 Axios + TypeScript 项目中常见的写法。
5.3 环境变量配置 baseURL
真实项目通常不会直接:
1
| baseURL: 'http://localhost:8000'
|
因为开发环境可能是:
测试环境可能是:
1
| https://test-api.example.com
|
生产环境可能是:
Vite 项目可以使用环境变量。
例如:
写:
1
| VITE_API_BASE_URL=http://localhost:8000/api
|
生产:
写:
1
| VITE_API_BASE_URL=https://api.example.com/api
|
Axios:
1 2 3 4
| const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 })
|
这样:
1 2 3
| 开发环境 → 本地 API
生产环境 → 正式 API
|
不用修改代码。
六、文件、请求控制与高级功能
6.1 文件上传
文件上传通常使用:
例如:
1 2 3
| const formData = new FormData()
formData.append('file', file)
|
然后:
1 2 3 4
| await axios.post( '/api/upload', formData )
|
例如 Vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function uploadFile(event) { const file = event.target.files[0]
const formData = new FormData()
formData.append('file', file)
const response = await axios.post( '/api/upload', formData )
console.log(response.data) }
|
HTML:
1 2 3 4
| <input type="file" @change="uploadFile" />
|
如果后端还要求其他参数:
1 2 3 4 5
| formData.append('file', file)
formData.append('userId', '1001')
formData.append('description', '头像')
|
都可以一起传递。
6.2 上传进度
Axios 支持监听上传进度。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| await axios.post( '/api/upload', formData, { onUploadProgress(event) { if (!event.total) { return }
const percent = Math.round( event.loaded * 100 / event.total )
console.log(`上传进度:${percent}%`) } } )
|
可以用于实现:
1 2 3 4 5 6 7
| 上传进度:10%
上传进度:30%
上传进度:72%
上传进度:100%
|
然后配合进度条组件展示上传进度。
6.3 文件下载
例如后端提供:
返回 Excel、PDF、ZIP 等文件。
Axios:
1 2 3 4 5 6
| const response = await axios.get( '/api/files/report', { responseType: 'blob' } )
|
这里:
不能省略。
然后:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const blob = new Blob([response.data])
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'report.xlsx'
a.click()
URL.revokeObjectURL(url)
|
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| async function downloadFile() { const response = await axios.get( '/api/files/report', { responseType: 'blob' } )
const blob = new Blob([ response.data ])
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url link.download = 'report.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url) }
|
6.4 取消 Axios 请求
某些请求可能非常慢。
例如:
1 2 3 4 5 6 7
| AI 对话
搜索建议
文件处理
复杂统计查询
|
用户切换页面以后,旧请求可能已经不需要了。
现代 Axios 可以配合:
取消请求。
例如:
1 2 3 4 5
| const controller = new AbortController()
axios.get('/api/users', { signal: controller.signal })
|
取消:
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const controller = new AbortController()
async function loadData() { try { const response = await axios.get( '/api/data', { signal: controller.signal } )
console.log(response.data) } catch (error) { if (axios.isCancel(error)) { console.log('请求已取消') return }
console.error(error) } }
loadData()
|
需要取消:
6.5 搜索场景为什么需要取消请求
假设用户连续输入:
程序可能连续发送:
1 2 3 4 5 6 7
| /api/search?keyword=a
/api/search?keyword=ax
/api/search?keyword=axi
/api/search?keyword=axios
|
如果:
请求先返回,
然后:
这个旧请求才返回,
页面可能反而显示旧搜索结果。
因此可以在新请求开始前取消旧请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let controller
async function search(keyword) { if (controller) { controller.abort() }
controller = new AbortController()
const response = await axios.get( '/api/search', { params: { keyword },
signal: controller.signal } )
return response.data }
|
搜索框、自动补全等功能常用这种做法。
6.6 并发请求
有时一个页面同时需要多个接口:
可以:
1 2 3 4 5 6 7 8 9
| const [ userResponse, messagesResponse, notificationsResponse ] = await Promise.all([ axios.get('/api/user'), axios.get('/api/messages'), axios.get('/api/notifications') ])
|
相比:
1 2 3 4 5
| await axios.get('/api/user')
await axios.get('/api/messages')
await axios.get('/api/notifications')
|
后者会一个接一个执行:
1 2 3 4 5 6 7
| 请求1完成 ↓ 请求2开始 ↓ 请求2完成 ↓ 请求3开始
|
而:
可以让多个请求并发执行:
1 2 3 4 5
| 请求1 ─────→
请求2 ─────→
请求3 ─────→
|
如果接口之间没有依赖关系,这通常更加高效。
6.7 请求之间存在依赖怎么办
例如:
先登录:
1 2 3 4 5 6 7
| const loginResult = await axios.post( '/api/login', { username: 'admin', password: '123456' } )
|
然后取得 Token:
1
| const token = loginResult.data.token
|
再查询用户:
1 2 3 4 5 6 7 8
| const user = await axios.get( '/api/user', { headers: { Authorization: `Bearer ${token}` } } )
|
这两个接口存在依赖,所以不能使用:
必须按照顺序执行。
6.8 Cookie 请求
如果后端使用 Cookie、Session 等方式认证,跨域场景可能需要:
例如:
1 2 3 4
| const request = axios.create({ baseURL: 'http://localhost:8000', withCredentials: true })
|
或者:
1 2 3
| axios.get('/api/user', { withCredentials: true })
|
不过这里需要后端的 CORS 同时正确配置,否则浏览器仍然会阻止请求。
七、开发环境与项目配置
7.1 Axios 和跨域 CORS
很多刚学 Axios 的开发者都会遇到:
例如:
1
| Access to XMLHttpRequest has been blocked by CORS policy
|
这通常不是 Axios 本身的问题。
浏览器发现:
1 2 3
| 前端:
http://localhost:5173
|
请求:
1 2 3
| 后端:
http://localhost:8000
|
端口不同,因此属于不同 Origin。
跨域问题通常需要:
或者:
解决。
单纯修改 Axios 代码:
通常无法直接绕过浏览器 CORS 限制。
7.2 Vite 开发代理
例如 Vue + Vite 项目。
vite.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [ vue() ],
server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } })
|
Axios:
1 2 3
| const request = axios.create({ baseURL: '/api' })
|
然后:
浏览器访问:
1
| http://localhost:5173/api/users
|
Vite 代理到:
1
| http://localhost:8000/api/users
|
开发阶段常用这种方式。
7.3 Axios 默认配置
也可以直接修改 axios 全局配置:
1 2 3 4
| axios.defaults.baseURL = 'https://api.example.com'
axios.defaults.timeout = 10000
|
例如:
1 2 3
| axios.defaults.headers.common[ 'Authorization' ] = 'Bearer token'
|
不过大型项目更加推荐:
因为全局配置会影响所有 Axios 请求,而实例配置更加容易隔离。
7.4 多个后端怎么办
假设项目需要:
1 2 3 4 5
| 业务 API
AI API
文件服务 API
|
可以创建多个 Axios 实例。
业务:
1 2 3
| export const apiRequest = axios.create({ baseURL: '/api' })
|
AI:
1 2 3
| export const aiRequest = axios.create({ baseURL: '/ai' })
|
文件:
1 2 3
| export const fileRequest = axios.create({ baseURL: '/file' })
|
分别设置自己的:
1 2 3 4 5 6 7
| baseURL
timeout
headers
interceptors
|
这种方式比修改全局 Axios 更适合复杂项目。
7.5 AI 接口为什么可能需要更长 timeout
普通 CRUD API:
可能足够。
但是:
1 2 3 4 5 6 7
| LLM 推理
RAG 检索
图片生成
复杂数据分析
|
可能需要几十秒甚至更久。
例如:
1 2 3 4
| const aiRequest = axios.create({ baseURL: '/api/ai', timeout: 120000 })
|
即:
因此 timeout 不应该无脑整个项目统一设置,而应该根据 API 类型决定。
7.6 Axios 错误类型判断
TypeScript 中尤其推荐:
1 2 3 4 5 6 7 8 9 10 11
| import axios from 'axios'
try { await axios.get('/api/users') } catch (error) { if (axios.isAxiosError(error)) { console.log( error.response?.status ) } }
|
相比:
这种写法类型更加安全。
7.7 一个更加完整的 request.ts
把前面的内容合起来,可以写一个更接近真实项目的版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| import axios from 'axios'
const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 })
request.interceptors.request.use( config => { 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 result = response.data
if ( result.code !== undefined && result.code !== 200 ) { return Promise.reject( new Error( result.message || '请求失败' ) ) }
return result },
error => { if (!axios.isAxiosError(error)) { return Promise.reject(error) }
const status = error.response?.status
switch (status) { case 400: console.error('请求参数错误') break
case 401: console.error( '登录状态已失效' )
localStorage.removeItem( 'token' )
break
case 403: console.error('没有访问权限') break
case 404: console.error('接口不存在') break
case 500: console.error( '服务器内部错误' )
break
default: console.error( error.message || '网络请求失败' ) }
return Promise.reject(error) } )
export default request
|
这已经可以覆盖大部分普通前端项目。
7.8 项目推荐目录结构
一个 Vue 项目可以这样组织:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| src │ ├── api │ ├── auth.ts │ ├── user.ts │ ├── article.ts │ └── file.ts │ ├── utils │ └── request.ts │ ├── stores │ ├── router │ ├── views │ ├── components │ └── main.ts
|
职责:
负责定义:
例如:
1 2 3 4 5
| getUsers()
createUser()
deleteUser()
|
而:
负责:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Axios 实例
baseURL
timeout
Token
请求拦截器
响应拦截器
统一错误处理
|
页面只负责:
八、最佳实践与总结
8.1 不要直接在页面写大量 Axios 请求
不推荐:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script setup> import axios from 'axios'
async function getUsers() { const token = localStorage.getItem('token')
const response = await axios.get( 'http://localhost:8000/api/users', { headers: { Authorization: `Bearer ${token}` } } )
// ... } </script>
|
如果几十个页面都这么写:
1 2 3 4 5 6 7 8 9
| URL 重复
Token 重复
错误处理重复
baseURL 重复
接口难维护
|
推荐:
1
| const users = await getUsers()
|
然后把:
交给:
处理。
8.2 不要在每个 API 里面重复 try/catch
例如:
1 2 3 4 5 6 7 8
| export async function getUsers() { try { return await request.get('/users') } catch (error) { console.error(error) throw error } }
|
如果只是:
那么意义并不大。
因为统一错误处理已经可以放到:
里面。
接口层可以直接:
1 2 3
| export function getUsers() { return request.get('/users') }
|
页面真正需要针对业务做特殊处理的时候再:
1 2 3 4 5
| try { await getUsers() } catch (error) { // 当前页面专属逻辑 }
|
8.3 不要把所有 HTTP 200 都当作业务成功
这是常见的误区。
HTTP:
只能表示:
并不一定代表:
例如:
1 2 3 4 5 6 7
| HTTP 200
{ "code": 10003, "message": "余额不足", "data": null }
|
因此真实项目通常存在两层判断:
需要根据公司的接口规范统一处理。
8.4 Axios 与 Fetch 的区别
很多人会问:
1 2 3
| 既然浏览器已经有 fetch,
为什么还要 Axios?
|
两者都可以完成 HTTP 请求。
Fetch:
1 2 3 4 5 6 7 8 9 10 11 12
| const response = await fetch( '/api/users' )
if (!response.ok) { throw new Error( `HTTP Error: ${response.status}` ) }
const data = await response.json()
|
Axios:
1 2 3 4 5
| const response = await axios.get('/api/users')
const data = response.data
|
Axios 比较吸引人的地方通常包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 请求/响应拦截器
统一 baseURL
timeout 配置
自动 JSON 处理
统一实例
上传下载进度
同样支持 AbortController 取消(signal 写法与 Fetch 一致)
方便的请求配置
|
而 Fetch 的优势是:
1 2 3 4 5
| 浏览器原生
不需要额外依赖
Web 标准 API
|
因此并不存在:
或者:
更加合理的理解应该是:
1 2 3
| Fetch 更接近浏览器原生底层 API
Axios 更接近开箱即用的 HTTP 客户端
|
8.5 Axios 常见面试题
Axios 是什么?
Axios 是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node.js 环境中使用,常用于发送 HTTP 请求。
Axios 请求拦截器有什么作用?
可以在请求发送之前统一修改请求配置,例如:
1 2 3 4 5 6 7
| 添加 Token
添加公共 Header
添加 Trace ID
统一参数处理
|
Axios 响应拦截器有什么作用?
可以统一处理服务器响应,例如:
1 2 3 4 5 6 7
| 提取 data
处理业务状态码
处理 401
统一错误提示
|
params 和 data 有什么区别?
一般:
用于 URL Query:
而:
用于 Request Body:
Axios 如何取消请求?
使用:
例如:
1 2 3 4 5 6 7 8
| const controller = new AbortController()
axios.get('/api/users', { signal: controller.signal })
controller.abort()
|
Axios 如何统一添加 Token?
通常使用请求拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13
| request.interceptors.request.use( config => { const token = localStorage.getItem('token')
if (token) { config.headers.Authorization = `Bearer ${token}` }
return config } )
|
Axios 如何处理 Token 失效?
通常通过响应拦截器监听:
然后:
1 2 3 4 5
| 删除本地 Token
清除用户状态
跳转登录页
|
8.6 Axios 学习重点
如果只是为了正常进行 Vue 或 React 项目开发,不需要把 Axios 每个配置都背下来。
首先需要熟练掌握:
1 2 3 4 5 6 7
| axios.get()
axios.post()
axios.put()
axios.delete()
|
然后掌握:
1 2 3 4 5 6 7 8 9
| params
data
headers
timeout
baseURL
|
再掌握工程化核心:
1 2 3 4 5 6 7 8 9 10 11
| axios.create()
请求拦截器
响应拦截器
Token
统一错误处理
API 模块封装
|
最后再学习:
1 2 3 4 5 6 7 8 9 10 11
| 文件上传
文件下载
AbortController
并发请求
TypeScript 泛型
多 Axios 实例
|
做到这里,普通前端项目中的 Axios 基本就够用了。
8.7 一个完整请求到底经历了什么
最后,把 Axios 整个工作流程串起来。
用户打开页面:
调用:
进入:
执行:
进入 Axios:
请求拦截器执行:
1 2 3 4 5
| 读取 Token
↓
添加 Authorization
|
然后:
发送到:
后端查询数据库:
返回:
Axios 响应拦截器执行:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 判断 HTTP 状态
↓
判断业务状态
↓
处理 401 / 403 / 500
↓
提取数据
|
最后:
得到真正需要的数据。
整个过程可以表示为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| Vue 页面
↓
API 模块
↓
Axios Instance
↓
Request Interceptor
↓
HTTP Request
↓
Backend
↓
Database
↓
HTTP Response
↓
Response Interceptor
↓
API 模块
↓
Vue 页面
|
8.8 总结
Axios 最基础的用法很简单:
1 2
| const response = await axios.get('/api/users')
|
项目开发中更要紧的是把 Axios 组织成一套可维护的请求体系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| axios.create()
↓
统一 baseURL
↓
统一 timeout
↓
请求拦截器
↓
自动携带 Token
↓
发送 HTTP 请求
↓
响应拦截器
↓
统一业务状态判断
↓
统一错误处理
↓
API 模块
↓
Vue / React 页面
|
因此学习 Axios 时,建议重点掌握:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Axios 实例
拦截器
Token
错误处理
API 模块化
TypeScript 类型
文件上传下载
请求取消
|
这些是进入企业项目以后使用频率最高的部分。