[toc]
| 单词 | 词性 | 音标 | 含义 |
|---|---|---|---|
| interceptor | n. | /ˌɪntəˈseptə(r)/ | 拦截器,在请求发出前 / 响应返回后统一插入处理 |
| request | n. | /rɪˈkwest/ | 请求,请求拦截器用于统一携带 token |
| response | n. | /rɪˈspɒns/ | 响应,响应拦截器统一处理成功与错误 |
| localStorage | n. | /ˈləʊkl ˈstɔːrɪdʒ/ | 浏览器本地存储,刷新、关闭浏览器都不丢 |
| token | n. | /ˈtəʊkən/ | 登录凭证,存 localStorage 并随请求头带上 |
| reject | v. | /rɪˈdʒekt/ | 拒绝,业务失败时 reject 让调用方进入 catch |
| Authorization | n. | /ˌɔːθəraɪˈzeɪʃn/ | 请求头字段,用于携带 Bearer token |
Lecture16-axios 进阶(拦截器、错误处理、参数)
1、本节讲什么
把 axios 再"武装"一下:用拦截器统一处理"每个请求都带 token、每个错误都弹提示",并搞清响应拦截器里"逻辑成功和逻辑失败"是两回事;最后把所有接口函数收进 src/api/,让页面不出现裸 http.get。
2、概念速览
什么是拦截器(interceptor):在请求发出前、响应返回后,统一"插一脚"做点事——给每个请求自动带 token、每个错误自动弹提示。不用每个页面写一遍。
类比:像 SpringMVC 的拦截器 / AOP——在方法执行前后统一处理通用逻辑。
什么是 localStorage:浏览器提供的一个"本地存储"对象,用 key-value 存数据,刷新页面、关掉浏览器都不会丢。
- 类比:像 Java 后端的"持久化存储"——服务器重启数据还在;localStorage 就是浏览器里的"小仓库"。
- 三个最常用方法:
localStorage.setItem('token', 'abc') // 存:setItem(键, 值)
localStorage.getItem('token') // 取:返回 'abc',没有则返回 null
localStorage.removeItem('token') // 删:按键删掉
- 注意:存的是字符串。存对象要
JSON.stringify(obj)转成字符串,读回来再JSON.parse(str)转回对象。 - 正确 vs 误区:localStorage 是"浏览器本地、这个浏览器这台机器"的,换浏览器/换设备没有;它主要用来存"刷新不能丢"的东西(如登录 token、记住的用户名),别当普通变量随手用。
关键认知:HTTP 成功 ≠ 业务成功。 后端通常返回统一响应体,形如:
{ "code": 200, "msg": "ok", "data": { ... } }
HTTP 200:请求到达了后端、后端没崩——网络层成功,会进响应拦截器的"成功回调"。code === 200:业务逻辑成功,才真正拿到想要的数据。code !== 200(如{ code: 400, msg: "库存不足" }):业务失败了,但 HTTP 还是 200。
所以成功回调里还要再判断一次 code——这正是本节第一件事。
正确 vs 误区:错误处理集中在拦截器,而不是每个页面都 try/catch。页面只管"成功后的业务"。
3、先看效果:一段"标准封装"
src/api/http.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'
const http = axios.create({ baseURL: '/api', timeout: 5000 })
// 请求拦截器:每个请求发出前,自动带上 token
http.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// 响应拦截器:HTTP 成功 / 失败 都要处理,HTTP 成功里还要区分"逻辑成功 / 逻辑失败"
http.interceptors.response.use(
// —— 成功回调:HTTP 200,但不代表业务成功 ——
(res) => {
const body = res.data // 统一响应体 { code, msg, data }
if (body.code === 200) {
return body.data // 情况A:逻辑成功 → 返回业务数据,调用处直接拿
}
// 情况B:HTTP 成功但业务失败(如库存不足、没权限)
ElMessage.error(body.msg)
return Promise.reject(body.msg) // 抛给调用方,走 catch
},
// —— 失败回调:HTTP 层面失败(404 / 500 / 网络断开…)——
(err) => {
const status = err.response?.status
if (status === 401) { ElMessage.error('登录已过期,请重新登录') }
else if (status === 500) { ElMessage.error('服务器出错了') }
else { ElMessage.error(err.message) }
return Promise.reject(err) // 仍要 reject,让调用方能感知失败
},
)
export default http
4、照着做
① 先动手认识 localStorage:打开浏览器 DevTools 的 Console,逐行试:
localStorage.setItem('token', 'abc') // 存
localStorage.getItem('token') // 取 → 'abc'
localStorage.removeItem('token') // 删
localStorage.getItem('token') // → null
再到 Application → Local Storage 面板里看刚才存的键值——这就是请求拦截器里 token 的存放位置。
② 用上面的代码替换 src/api/http.ts。
③ 试一个"业务失败"的请求(后端返回 code !== 200),看是否弹 msg 里的提示、且调用处能 catch 到——因为成功回调里 Promise.reject 了。
④ 把接口函数收进 src/api/,页面不出现裸 http.get。
新建 src/api/user.ts:
import http from './http'
export interface User {
id: number
name: string
age: number
}
// 所有接口函数都收在这里:页面只 import 函数,不碰 http
export function fetchUsers(keyword = '') {
return http.get('/users', { params: { keyword } })
}
export function createUser(data: Partial<User>) {
return http.post('/users', data)
}
export function deleteUser(id: number) {
return http.delete(`/users/${id}`)
}
页面里只 import 函数:
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { fetchUsers } from '../api/user' // 只 import 函数,不出现 http
const list = ref<User[]>([])
onMounted(async () => {
list.value = await fetchUsers('红') // 直接拿数据(拦截器已返回业务数据)
})
</script>
⑤ 学传参数的几种写法:
// GET 带 query 参数:?keyword=xx&page=1
await http.get('/users', { params: { keyword: '红', page: 1 } })
// POST 带 body(表单数据)
await http.post('/users', { name: '小明' })
// 路径参数
await http.delete('/users/5') // 删除 id=5
await http.put('/users/5', { age: 23 }) // 编辑 id=5
5、常见疑问
-
Q:成功回调里,情况A和情况B怎么区分? A:看后端统一响应体里的
code。code === 200(或团队约定的成功码)→ 情况A,return body.data;否则 → 情况B,Promise.reject。先和后端对齐"成功码是几"。 -
Q:为什么情况B要
Promise.reject? A:因为调用处用await等结果。不 reject 的话,调用处会以为"成功了"继续走成功分支,拿到的却是失败数据。reject才能让它进catch,正确感知"这次失败了"。 -
Q:
return res.data.data和return body.data有什么区别? A:一回事。res.data是 HTTP 响应体(即统一响应体),body.data(或res.data.data)是响应体里真正要的业务数据。取决于后端包了几层,先打印res.data看结构。 -
Q:接口函数收进
src/api/,页面不出现裸http.get,好处是什么? A:① 接口路径、参数只在src/api/user.ts出现一次,后端改了只改一处;② 页面代码更干净,只看到"业务函数"(fetchUsers),看不到"HTTP 细节"(/users?keyword=)。规范"数据收口"。 -
Q:token 从哪来、存哪? A:登录成功后后端在响应体中返回 token 数据,前端接收到 token 就存
localStorage(刷新不丢)或 Pinia。请求拦截器每次带上。 -
Q:为什么 token 放
localStorage,不放普通变量? A:普通变量(内存)刷新页面就没了,token 丢了就等于登录态丢了、要重新登录。localStorage刷新不丢,所以登录凭证放这里。存对象记得JSON.stringify/JSON.parse。总之localStorage在浏览器端,可以长期存储数据。 -
Q:
localStorage和sessionStorage有什么区别? A:都能在浏览器本地存 key-value。区别在存活期:localStorage关掉浏览器还在(下次打开仍在);sessionStorage只在这个"标签页会话"内有效,关标签页就清空。token 用localStorage。 -
Q:拦截器会不会影响调试? A:会多一点"黑盒",但更好排查——你只需看"请求有没有发出、响应有没有回来",通用的都集中了。
-
Q:如何从响应 error 对象中解析到我们关心的错误信息?
A:参考下面的结构图

6、规范要点
- 请求/响应拦截器集中放
src/api/http.ts,token、超时、错误提示这类"横切逻辑"写一次。 - 响应拦截器必须区分逻辑成功/失败:成功
return body.data,业务失败Promise.reject(msg),别让调用方误判。 - 所有接口函数收进
src/api/(如src/api/user.ts),页面只import函数,不出现裸http.get。
7、本节验证
- 会用请求拦截器给每个请求自动带 token ✅
- 会在响应拦截器区分“逻辑成功 / 逻辑失败”:成功 return body.data、失败 Promise.reject ✅
- 会把接口函数收进 src/api/,页面不出现裸 http.get ✅
- 会用 localStorage 存 token(刷新不丢) ✅
- 认识“HTTP 成功 ≠ 业务成功” ✅