4.8 KiB
4.8 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-04-25 14:30 |
统一封装请求:apiRequest 函数
概述
把前端所有接口调用中重复的逻辑抽到一个公共函数里,让每个页面只需要关心「调哪个接口、传什么参数」。
💡 想一想:如果每个页面都自己写一遍 token 拼接、错误处理、JSON 解析,代码会变得怎样?
答案是——重复、易错、难维护。所以业界的标准做法就是:抽出一个通用请求函数。
核心代码
export async function apiRequest<T>(path: string, init?: RequestInit): Promise<T> {
function getAuthToken() {
return window.localStorage.getItem(TOKEN_STORAGE_KEY) ?? ''
}
const response = await fetch(`${API_BASE}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
...(getAuthToken() ? { Authorization: `Bearer ${getAuthToken()}` } : {}),
...(init?.headers ?? {}),
},
})
const result = (await response.json()) as ApiResponse<T>
if (!response.ok || result.code !== 200) {
throw new Error(result.msg || '请求失败')
}
return result.data
}
逐行拆解:这段代码在做什么
1. 泛型 <T> — 让调用方决定返回值类型
export async function apiRequest<T>(path: string, init?: RequestInit): Promise<T>
path:接口路径,比如'/users'init:fetch 配置项,比如method、body<T>:返回值类型由调用方指定
好处:调用时可以自动获得类型提示和类型检查。
2. 自动读取 token
function getAuthToken() {
return window.localStorage.getItem(TOKEN_STORAGE_KEY) ?? ''
}
从本地存储取出登录凭证。有 token 就返回 token,没有就返回空字符串。
3. 拼接完整请求地址
const response = await fetch(`${API_BASE}${path}`, { ... })
把基础地址和接口路径拼在一起:
| 变量 | 值 |
|---|---|
API_BASE |
'http://localhost:3000/api' |
path |
'/login' |
| 最终 URL | 'http://localhost:3000/api/login' |
4. 合并请求配置(重点)
{
...init, // 外部传的配置
headers: {
'Content-Type': 'application/json', // 统一 JSON 格式
...(getAuthToken() ? { Authorization: `Bearer ${getAuthToken()}` } : {}), // 自动加 token
...(init?.headers ?? {}), // 合并外部请求头
},
}
层层展开,体现一个设计思想:统一封装但允许扩展。
| 优先级 | 顺序 | 说明 |
|---|---|---|
| 1 | 最低 | 'Content-Type' 统一设为 JSON |
| 2 | 中间 | 有 token 就自动带上 Authorization |
| 3 | 最高 | 调用方自定义的请求头可以覆盖 |
5. 统一解析响应
const result = (await response.json()) as ApiResponse<T>
默认后端返回格式为:
{
"code": 200,
"msg": "success",
"data": { ... }
}
6. 统一判断成功失败
if (!response.ok || result.code !== 200) {
throw new Error(result.msg || '请求失败')
}
做了双重判断:
- HTTP 层面:
response.ok检查状态码是否在 200~299 - 业务层面:
result.code === 200检查后端返回的业务状态
任意一个失败就抛错,页面层不用再写重复的判断逻辑。
7. 只返回业务数据
return result.data
最终直接返回 data 字段,调用方拿到的就是最干净的数据。
请求流程
flowchart LR
A[调用 apiRequest] --> B[读取本地 token]
B --> C[拼接完整 URL]
C --> D[合并请求配置和请求头]
D --> E[发起 fetch 请求]
E --> F[解析 JSON 响应]
F --> G{是否成功?}
G -->|HTTP 或 code 异常| H[抛出错误]
G -->|都正常| I[返回 data]
H --> J[页面层捕获错误]
I --> K[页面层处理业务数据]
实际调用示例
获取课程列表:
const courses = await apiRequest<Course[]>('/courses')
提交表单:
const user = await apiRequest<User>('/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
})
调用方不需要关心:
- token 怎么加
- JSON 怎么解析
- 错误怎么处理
总结:这段封装的本质
把**「请求的共性」抽出来,变成标准入口;
把「接口的差异」**保留给调用参数。
具体体现在四个层面:
| 层面 | 封装内容 |
|---|---|
| 函数抽象 | apiRequest<T> 统一入口 |
| 配置合并 | headers、URL 自动拼接 |
| 错误统一处理 | HTTP 状态 + 业务 code 双重判断 |
| 返回值格式化 | 只返回 data,屏蔽外围结构 |