axios基础
Axios是一个流行的HTTP客户端库,用于前端与后端服务器的数据交互。本文介绍了Axios的核心概念、优势对比和基础使用方法。 核心优势:相比XMLHttpRequest、jQuery和Fetch API,Axios具有自动数据转换、准确错误处理和功能全面的特点,成为行业标准工具。 快速上手: 可通过CDN或npm安装 无需在main.ts注册,直接导入使用 支持GET/POST/PUT/DEL
1. Axios 核心概览
1.1 Axios 是什么
在网页开发中,我们需要从服务器获取数据。Axios 就是连接前端页面和后端服务器的那座“桥梁”。
(1) 身份
Axios 是一个第三方库,某团队的一个开源项目,主流框架(如Vue,React)都推荐使用这个。
(2) 特征
Axios 是异步的。它在请求数据时,不会前端网页,用户依然可以进行其他操作。
1.2 为什么选它
(1) 技术对比
在 Axios 出现之前,还有 XMLHttpRequest、jQuery、Fetch API 等
| 阶段 | 工具 | 缺点) |
|---|---|---|
| 1. 原始社会 | XHR | 代码太长、嵌套地狱 |
| 2. 封建时代 | jQuery | 捆绑销售(为了个请求得下载整个库) |
| 3. 现代雏形 | Fetch | 半成品(不报错、得手动转格式) |
| 4. 现代文明 | Axios | 没缺点:自动转格式、报错准、功能全 |
(2) 学习理由
- 行业标准: 虽然有 Fetch(原生)和 TanStack Query(进阶管理),但 Axios 仍是目前企业级项目中使用最广、最稳的基础库。
- 一通百通: Axios 的设计理念(拦截器、Promise 链)是网络请求的“通用模板”,学会它,换任何工具都能秒上手。
- 跨端神技: 一套代码在浏览器(前端)和 Node.js(后端)都能跑,性价比极高。
2. 快速上手
2.1 安装与集成
(1) 引入
引入 Axios 这类热门的前端库,基本上都有两种方式
a. CDN 引入(个人练习)
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
b. 直接下载到本地项目中(正式项目))
// (npm为例)
npm install axios
(2) 无需在 main.ts 中引入
第三方库有两种:插件和普通库,普通库可以在各种框架使用,插件和某一个框架强绑定,插件都需要在main.ts引入。
像 Pinia、Router 等属于 Vue 的插件,Axios 属于普通第三方库,Element Plus 既能当插件也能当普通库。
| 维度 | Vue 插件 (Pinia/Router/Element) | 普通库 (Axios/Lodash/Element) |
|---|---|---|
| 是否依赖 Vue | 必须依赖,离开 Vue 跑不通 | 独立存在,哪里都能跑 |
| main.ts 注册 | 必须 (app.use) | 不需要 |
| 使用方式 | 全局可用,或通过 Vue 钩子调用 | 哪里用到,哪里 import |
| 类比 | 房子的中央空调系统,与房子绑定 | 房间里的一台电风扇,可以拿到别的房子用 |
2.2 基础请求方法
(1) 请求的结构
请求包含三部分:请求行、请求头、请求体
a. 请求行:
- Method (方法如GET)
- URL (路径如
/api/v1/user/profile) - **Protocol **(协议通常是
HTTP/1.1或HTTP/2)
b. 请求头:
| 标题 (Header) | 含义 | 举例 |
|---|---|---|
| Host | 请求的目标服务器地址 | api.jimidou.com |
| Content-Type | 【极重要】 告诉服务器发过去的数据是什么格式 | application/json (JSON数据) 或 multipart/form-data (传文件) |
| Authorization | 【最常用】 身份验证令牌(Token) | Bearer eyJhbGciOiJIUzI1... (后端验证身份的唯一依据) |
| User-Agent | 告诉服务器你用的是什么浏览器或设备 | Mozilla/5.0 (Windows NT 10.0; Win64; x64)... |
| Accept | 告诉服务器你希望接收什么格式的回包 | application/json, text/plain, */* |
c. 请求体:
GET 请求的请求体通常是空的。POST/PUT 请求装载着具体的业务数据。
{
"username": "zhangsan",
"password": "password123"
}
(2) 四种请求方法
Axios 的请求结构有默认的数据,需要配置的东西很少,比如Method、URL 、Authorization、请求体。
a. GET:获取数据
import axios from 'axios'
const getList = async () => {
// await 必须包在 async 函数里
try {
// 1. 等待请求结果,直接把货给 res
const res = await axios.get(
'/api/question/list',
{
params: { page: 1 },
headers: {
'Authorization': 'Bearer xxx',
'Accept-Language': 'zh-CN'
},
timeout: 5000
}
)
// 2. 只有成功了才会运行到这一行
console.log('拿到了:', res.data)
} catch (err) {
// 3. 如果请求失败(比如 404 或超时),会直接跳到这里
console.error('出错了:', err)
}
}
b. POST:提交表单/JSON
POST 通常用于发送 JSON 对象。它的第二个参数就是 数据本身,第三个参数才是 配置项。
const addQuestion = async () => {
try {
const res = await axios.post(
'/api/question/add',
{
// 第二个参数:请求体 (Body)
title: '如何学习 Axios?',
content: '我觉得吃透请求结构很重要。'
},
{
// 第三个参数:配置项 (Config)
headers: { 'Authorization': 'Bearer xxx' },
timeout: 5000
}
);
console.log('创建成功:', res.data);
} catch (err) {
console.error('提交失败:', err);
}
}
c. PUT:更新
PUT 的结构和 POST 一模一样。它通常用于修改已经存在的数据(比如改个标题、换个头像)。
const updateQuestion = async (id) => {
try {
const res = await axios.put(
`/api/question/update/${id}`, // 路径参数写法
{ title: '修改后的标题' }, // 请求体
{ headers: { 'Authorization': 'Bearer xxx' } } // 配置项
);
console.log('修改成功');
} catch (err) {
console.error('修改失败');
}
}
d. DELETE:删除
流派 1:参数在路径里(最推荐,最常用)
const deleteById = async (id) => {
try {
// 像 GET 一样简单,只有两个参数:URL 和 Config
await axios.delete(`/api/question/delete/${id}`, {
headers: { 'Authorization': 'Bearer xxx' }
});
console.log('删除成功');
} catch (err) {
console.error('删除失败');
}
}
流派 2:参数在请求体里(偶尔见,需注意) 如果后端非要你传个 data: { id: 1 } 才能删:
await axios.delete('/api/question/delete', {
headers: { 'Authorization': 'Bearer xxx' },
data: { id: 123 } // 必须写在 data 属性里!
});
(3) 两种 Axios 的写法
a. then 的方式(繁琐)
import axios from 'axios'
// 1. 发起请求
axios.get('/api/question/list', {
params: { page: 1 }, // 对应 URL 里的查询参数
headers: {
'Authorization': 'Bearer xxx', // 手动塞入 Token
'Accept-Language': 'zh-CN' // 手动指定语言,给后端看的用来返回不同数据库的数据,99%的网站没必要
},
timeout: 5000 // 这一次请求如果 5 秒没回就掐断
})
.then(res => {
// 2. 成功回执:res 是整个包裹,res.data 是后端给的货
console.log('拿到了:', res.data)
})
.catch(err => {
// 3. 失败回执:断网、超时、404 都会跑这里
console.error('出错了:', err)
})
即使封装成方法,也要写then
// 定义方法
const getListByThen = () => {
// 注意:这里必须 return,否则外部拿不到结果
return axios.get('/api/list').then(res => {
return res.data; // 返回处理后的数据
});
}
// 调用方法
getListByThen().then(data => {
console.log('拿到了数据:', data);
});
计算机执行代码的速度极快,而网络请求相对于 CPU 来说慢得像蜗牛。
- 第 0 毫秒:你调用了
getListByThen()。 - 第 1 毫秒:Axios 把请求发向了互联网。
- 第 2 毫秒:重点来了!
getListByThen执行完毕。因为它是一个异步函数,它不会等网络结果,而是立刻返回了一个 Promise 对象。此时,你的list变量里装的就是这个对象。 - 第 500 毫秒:服务器的数据终于顺着网线回来了,触发了你函数内部的
.then()。
结论:你在第 2 毫秒试图拿 list 去用,但数据在第 500 毫秒才到。你拿到的只能是那个“空支票”。
b. await 方式,简洁易懂
import axios from 'axios'
const getList = async () => {
// await 必须包在 async 函数里
try {
// 1. 等待请求结果,直接把货给 res
const res = await axios.get('/api/question/list', {
params: { page: 1 },
headers: { 'Authorization': 'Bearer xxx' },
timeout: 5000
})
// 2. 只有成功了才会运行到这一行
console.log('拿到了:', res.data)
} catch (err) {
// 3. 如果请求失败(比如 404 或超时),会直接跳到这里
console.error('出错了:', err)
}
}
这就是 await 的优势。它在底层帮你做了一件事:强制让当前代码行“停工”,等到那张“空支票”兑现成“现金”后,再把现金赋值给左边的变量。
3. 进阶实战
每次写 Axios 都写 headers、timeout等这么多内容过于复杂且臃肿,在项目中,通常需要对其进行封装。
http/
├── request.ts # 拦截器等axios的第一层封装
├── api.ts # axios的第二层封装(封装了4种方法)
├── modules/ # 封装各个具体的后端api方法
└── index.ts # 暴露所有的api方法
3.1 request.ts
(1) 创建 Axios 实例
baseURL 本地开发中写在 vite.config.ts 里面,上线时写在 nginx.conf 里面,/api 的意义在于替换为真正的地址。
创建实例时可以放入一些固定的初始化数据,比如 baseURL,请求行、请求头的固定的数据都可以在这里修改,不写则是用默认值。
const instance: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 5000,
});
(2) 请求拦截器
这部分的主要作用是把 Token 放入 请求头的 Authorization 中,因为每次请求的 Token 可能不一样,所以每次请求都要重新放入。
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const { accessToken } = useAuthStore()
if (!accessToken) return config
config.headers ??= {} as AxiosRequestHeaders
config.headers.Authorization = `Bearer ${accessToken.trim()}`
return config
},
(error: AxiosError) => {
console.error('请求拦截器错误:', error)
return Promise.reject(error)
}
)
a. (error: AxiosError)作用
- 传递信号:它是个“传声筒”。捕获的是上一个拦截器抛出的失败信号。
- 打破死等:必须写
return Promise.reject(error)。这样如果前面有人取消了请求或拦截了请求,页面上的await才能立刻收到“失败”通知,而不是在那转圈死等。 - 边界警告:它不负责抓取当前
config逻辑里的 JS 崩溃(如trim()报错),那种错会直接导致程序炸裂。
b. 请求拦截器什么情况下会走(error: AxiosError)
基本上不会走的,直接理解成不用我们管,系统自己会处理
-
一个页面有很多请求,某个请求
(error: AxiosError)了,后续依赖此请求的请求全都(error: AxiosError)。举例:只有请求2是建立在请求1的基础上,请求2的
(error: AxiosError)才会依据请求1而不是自己去后端碰墙。针对并发的请求来说,即使请求2慢了两秒,即在请求1已经(error: AxiosError)了,因为二者没有依赖关系所以请求2依然会自己去后端撞墙? -
代码里写的
throw或者Promise.reject,比如config里面写if (!window.navigator.onLine) { return Promise.reject(new Error('没网发个毛请求')); } -
Axios 内部的“规则校验”
(3) 响应拦截器
// 标记是否正在刷新token
let isRefreshing = false
// 等待队列,存放等待刷新token的请求的 Promise 控制函数
type FailedQueueItem = {
resolve: (token: string | PromiseLike<string>) => void
reject: (error: any) => void
}
let failedQueue: FailedQueueItem[] = []
function processQueue(error: any, token: string | null = null) {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error)
} else if (token !== null) {
resolve(token)
} else {
reject(new Error('No token to resolve'))
}
})
failedQueue = []
}
/**
* 响应拦截器
* interceptors.response.use(successFn, errorFn)
*/
instance.interceptors.response.use(
(response: AxiosResponse) => {
console.log('响应拦截器:', response)
return response
},
async (error: AxiosError) => {
console.error('响应拦截器错误:', error.response?.data)
// 是否调用刷新 token 方法
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }
// 无响应处理
if (!error.response) {
ElNotification.error({
title: '网络错误',
message: '服务器无响应,请检查网络或稍后重试'
})
return Promise.reject(error)
}
const status = error.response.status
// 401 且没有刷新过 token,尝试刷新 token
if ((status === 401) && !originalRequest._retry) {
originalRequest._retry = true
// 正在刷新 token,将请求加入等待队列
if (isRefreshing) {
return new Promise<string>((resolve, reject) => {
failedQueue.push({ resolve, reject })
})
.then(token => {
if (!originalRequest.headers) {
originalRequest.headers = {} as AxiosRequestHeaders
}
originalRequest.headers.Authorization = `Bearer ${token}`
return instance(originalRequest)
})
.catch(err => Promise.reject(err))
}
// 正在刷新 token
isRefreshing = true
try {
const { data } = await instance.post('/users/refresh-token')
const newToken = data?.data?.accessToken
if (!newToken) {
throw new Error('刷新 token 失败,未返回新的 accessToken')
// 弹出重新登陆提示框
}
const authStore = useAuthStore()
authStore.setToken(newToken)
// 队列中的请求换上新 token 继续执行
processQueue(null, newToken)
if (!originalRequest.headers) {
originalRequest.headers = {} as AxiosRequestHeaders
}
originalRequest.headers.Authorization = `Bearer ${newToken}`
return instance(originalRequest)
} catch (refreshError) {
processQueue(refreshError, null)
const authStore = useAuthStore()
authStore.logout()
ElNotification.error({
title: '身份验证失败',
message: '登录状态已过期,请重新登录'
})
router.push('/')
return Promise.reject(refreshError)
} finally {
isRefreshing = false
}
}
// 其他错误统一提示
let message = '请求失败,请稍后重试'
if (error.response.data && typeof error.response.data === 'object') {
// 尽量取服务端的 message
if ('message' in error.response.data && typeof error.response.data.message === 'string') {
message = error.response.data.message
}
} else if (error.response.statusText) {
message = error.response.statusText
}
ElNotification.error({
title: `错误 ${status}`,
message
})
return Promise.reject(error)
}
)
a. 响应拦截器什么情况下会走(error: AxiosError)
- 只要 HTTP 响应状态码(
response.status)不在2xx范围内,就会触发error回调。 - 断网/DNS解析失败、跨域错误 (CORS)。这种情况
error.response是undefined。 - 请求超时 (Timeout)
- 主动取消请求
- 在请求拦截器里写了
throw new Error('缺少必要参数')或者return Promise.reject()。
业务 Code ≠ HTTP Status
- 走
success回调:HTTP Status = 200。哪怕后端 JSON 里写了{ "code": 500, "msg": "余额不足" },对 Axios 来说,这也是“一次成功的通信”。- 走
error回调:HTTP Status = 401/500 等。此时后端不仅要改 JSON,还得通过response.setStatus(401)修改 HTTP 封面。这部分通常写在后端的拦截器中
b. 常见错误状态码
- 401 (Unauthorized):没登录,或者 Token 过期了。
- 403 (Forbidden):登录了,没权限看这个页面。
- 404 (Not Found):请求的接口地址写错了。
- 500 (Internal Server Error):后端代码写炸了,服务器冒烟了。
更多推荐



所有评论(0)