This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

210 lines
4.8 KiB
Markdown
Raw Permalink Blame History

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