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
obsidian/金山办公作业/Week06/统一封装请求.md
T

4.8 KiB
Raw Blame History

tags, create time
tags create time
frontend
api
typescript
fetch
abstraction
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,屏蔽外围结构

关联笔记