|
1 | | -# Axios 封装 |
| 1 | +# @ethan-utils/axios |
2 | 2 |
|
3 | | -该包对 `axios` 进行了封装,提供了统一的 API 请求客户端。 |
| 3 | +高可用 axios 请求库,支持全局配置、请求拦截、响应拦截、自动重试、Token 注入、401 回调等功能,适用于前后端分离项目的 API 请求统一管理。 |
4 | 4 |
|
5 | 5 | ## 特性 |
6 | 6 |
|
7 | | -- 单例模式:通过 `initApiClient` 初始化,全局共享一个 `axios` 实例。 |
8 | | -- 统一响应格式:默认请求方法返回 `BaseResponse<T>` 格式的数据。 |
9 | | -- 统一错误处理:自动捕获请求错误并返回标准化的错误响应。 |
10 | | -- TypeScript 支持:提供完整的类型定义。 |
11 | | -- 提供不包装响应体的方法:为特殊场景,提供了 `...WithoutBaseResponse` 系列方法。 |
| 7 | +- **单例/多实例模式**:支持全局单例和多实例灵活切换。 |
| 8 | +- **标准响应与原始响应**:所有请求方法分为标准响应(`BaseResponse`)和原始响应(`raw`)两类。 |
| 9 | +- **自动重试**:网络错误和 5xx 状态自动重试。 |
| 10 | +- **Token 注入**:支持动态获取 Token 并自动注入请求头。 |
| 11 | +- **401 回调**:支持未授权自动回调处理。 |
| 12 | +- **TypeScript 完全类型支持**。 |
12 | 13 |
|
13 | 14 | ## 安装 |
14 | 15 |
|
15 | | -由于是在 monorepo 环境中,您可以直接在其他包的 `package.json` 中添加依赖: |
| 16 | +建议使用 pnpm 进行依赖管理: |
16 | 17 |
|
17 | | -```json |
18 | | -"dependencies": { |
19 | | - "@ethan-utils/axios": "workspace:*" |
20 | | -} |
| 18 | +```sh |
| 19 | +pnpm add @ethan-utils/axios axios axios-retry qs |
21 | 20 | ``` |
22 | 21 |
|
23 | | -然后运行 `pnpm install`。 |
| 22 | +> 依赖:axios、axios-retry、qs |
24 | 23 |
|
25 | | -## 使用方法 |
| 24 | +## 快速上手 |
26 | 25 |
|
27 | | -### 1. 初始化 |
| 26 | +### 1. 初始化全局请求客户端 |
28 | 27 |
|
29 | | -在应用入口处(例如 `main.ts` 或 `app.ts`),调用 `initApiClient` 来初始化请求客户端。 |
| 28 | +在应用入口(如 `main.ts` 或 `app.ts`)调用 `createRequest` 并设置为全局单例(默认即可),否则后续调用 `request` 会抛出错误。 |
30 | 29 |
|
31 | 30 | ```typescript |
32 | | -// src/main.ts |
33 | | -import { initApiClient } from "@ethan-utils/axios"; |
| 31 | +import { createRequest, request } from "@ethan-utils/axios"; |
34 | 32 |
|
35 | | -initApiClient({ |
| 33 | +createRequest({ |
36 | 34 | baseURL: "https://api.example.com", |
37 | 35 | timeout: 10000, |
38 | | - // 其他 axios 配置 |
| 36 | + getToken: () => localStorage.getItem("token"), // 可选,自动注入 Authorization |
| 37 | + onUnauthorized: () => { |
| 38 | + // 可选,401 未授权时的处理 |
| 39 | + window.location.href = "/login"; |
| 40 | + }, |
39 | 41 | }); |
| 42 | + |
| 43 | +// 之后可直接使用 request 进行请求 |
40 | 44 | ``` |
41 | 45 |
|
42 | 46 | ### 2. 发起请求 |
43 | 47 |
|
44 | | -在需要发起 API 请求的地方,导入 `request` 对象。 |
45 | | - |
46 | 48 | ```typescript |
47 | 49 | import { request } from "@ethan-utils/axios"; |
48 | 50 |
|
49 | | -interface User { |
50 | | - id: number; |
51 | | - name: string; |
| 51 | +// 标准响应(BaseResponse) |
| 52 | +const res = await request.get<User>("/users/1"); |
| 53 | +if (res.code === 200) { |
| 54 | + console.log(res.data); |
| 55 | +} else { |
| 56 | + console.error(res.msg); |
52 | 57 | } |
53 | 58 |
|
54 | | -// GET 请求 |
55 | | -async function getUser(id: number) { |
56 | | - const res = await request.get<User>(`/users/${id}`); |
57 | | - if (res.code === 200) { |
58 | | - console.log(res.data); // { id: 1, name: 'John Doe' } |
59 | | - } else { |
60 | | - console.error(res.msg); |
61 | | - } |
62 | | -} |
63 | | - |
64 | | -// POST 请求 |
65 | | -async function createUser(name: string) { |
66 | | - const res = await request.post<{ success: boolean }>("/users", { name }); |
67 | | - if (res.code === 201) { |
68 | | - console.log("User created"); |
69 | | - } else { |
70 | | - console.error(res.msg); |
71 | | - } |
| 59 | +// 原始响应(遇到错误直接抛出异常) |
| 60 | +try { |
| 61 | + const user = await request.raw.get<User>("/users/1"); |
| 62 | + console.log(user); |
| 63 | +} catch (e) { |
| 64 | + // 需自行处理异常 |
72 | 65 | } |
73 | 66 | ``` |
74 | 67 |
|
75 | | -### 3. 使用不带 BaseResponse 的方法 |
| 68 | +### 3. 多实例用法 |
76 | 69 |
|
77 | | -如果您的 API 端点没有遵循 `BaseResponse` 结构,可以使用 `WithoutBaseResponse` 系列方法。这些方法在成功时直接返回后端返回的 `data`,在失败时会直接抛出错误,需要您自行使用 `try...catch` 处理。 |
| 70 | +如需隔离不同 API 客户端,可用 `createRequest` 创建多实例: |
78 | 71 |
|
79 | 72 | ```typescript |
80 | | -import { request } from "@ethan-utils/axios"; |
| 73 | +import { createRequest } from "@ethan-utils/axios"; |
| 74 | + |
| 75 | +const api1 = createRequest({ baseURL: "https://api1.com" }, false); // 新实例 |
| 76 | +const api2 = createRequest({ baseURL: "https://api2.com" }, false); |
| 77 | + |
| 78 | +const res1 = await api1.get<any>("/foo"); |
| 79 | +const res2 = await api2.get<any>("/bar"); |
| 80 | +``` |
| 81 | + |
| 82 | +## API 说明 |
| 83 | + |
| 84 | +### 初始化 |
| 85 | + |
| 86 | +- `createRequest(options: CreateApiOptions, isSingleton = true)`:创建请求客户端,`isSingleton` 为 true 时返回全局单例,否则每次返回新实例。**如需初始化全局 request,直接调用一次即可。** |
| 87 | +- `request`:全局请求客户端代理,需先用 `createRequest` 初始化。 |
81 | 88 |
|
82 | | -// 假设 /profile 直接返回用户对象 |
83 | | -interface Profile { |
84 | | - username: string; |
85 | | - email: string; |
| 89 | +### 请求方法 |
| 90 | + |
| 91 | +所有方法均支持标准响应(BaseResponse)和原始响应(raw): |
| 92 | + |
| 93 | +- `get<T>(url, config?)` |
| 94 | +- `post<T>(url, data?, config?)` |
| 95 | +- `put<T>(url, data?, config?)` |
| 96 | +- `delete<T>(url, config?)` |
| 97 | +- `patch<T>(url, data?, config?)` |
| 98 | + |
| 99 | +原始响应方法通过 `raw` 命名空间调用: |
| 100 | + |
| 101 | +- `raw.get<T>(url, config?)` |
| 102 | +- `raw.post<T>(url, data?, config?)` |
| 103 | +- `raw.put<T>(url, data?, config?)` |
| 104 | +- `raw.delete<T>(url, config?)` |
| 105 | +- `raw.patch<T>(url, data?, config?)` |
| 106 | + |
| 107 | +#### 返回值说明 |
| 108 | + |
| 109 | +- 标准响应:`Promise<BaseResponse<T>>`,失败时 code 非 200/201,data 为 null,msg 为错误信息。 |
| 110 | +- 原始响应:`Promise<T>`,失败时直接抛出异常。 |
| 111 | + |
| 112 | +### 类型定义 |
| 113 | + |
| 114 | +```typescript |
| 115 | +/** |
| 116 | + * 标准化 API 响应结构 |
| 117 | + */ |
| 118 | +export interface BaseResponse<T> { |
| 119 | + data: T; // 实际数据 |
| 120 | + msg: string; // 提示信息 |
| 121 | + code: number; // 业务状态码 |
86 | 122 | } |
87 | 123 |
|
88 | | -async function getProfile() { |
89 | | - try { |
90 | | - const profile = await request.getWithoutBaseResponse<Profile>("/profile"); |
91 | | - console.log(profile.username); |
92 | | - } catch (error) { |
93 | | - // 自行处理错误 |
94 | | - console.error("Failed to fetch profile:", error); |
95 | | - } |
| 124 | +/** |
| 125 | + * 创建 API 实例的配置选项 |
| 126 | + */ |
| 127 | +export interface CreateApiOptions { |
| 128 | + baseURL: string; // API 的基础 URL |
| 129 | + getToken?: () => string | null; // 获取认证令牌的函数 |
| 130 | + onUnauthorized?: () => void; // 401 未授权时的回调 |
| 131 | + timeout?: number; // 请求超时时间 |
96 | 132 | } |
97 | 133 | ``` |
98 | 134 |
|
99 | | -## API |
| 135 | +## 注意事项 |
| 136 | + |
| 137 | +- **必须先初始化**:未初始化直接调用 `request` 会抛出 `API client has not been initialized. Please call createRequest() first.` |
| 138 | +- **Token 自动注入**:如需自动携带 token,传入 `getToken`。 |
| 139 | +- **401 处理**:如需自动跳转登录等,传入 `onUnauthorized`。 |
| 140 | +- **自动重试**:网络错误和 5xx 状态自动重试 3 次。 |
| 141 | +- **request.r**:`request` 还有一个简写别名 `r`,用法一致。 |
| 142 | + |
| 143 | +## 依赖 |
| 144 | + |
| 145 | +- axios |
| 146 | +- axios-retry |
| 147 | +- qs |
100 | 148 |
|
101 | | -`request` 对象包含以下方法: |
| 149 | +## 贡献与反馈 |
102 | 150 |
|
103 | | -- `get<T>(url, config)` |
104 | | -- `getWithoutBaseResponse<T>(url, config)` |
105 | | -- `post<T>(url, data, config)` |
106 | | -- `postWithoutBaseResponse<T>(url, data, config)` |
107 | | -- `put<T>(url, data, config)` |
108 | | -- `putWithoutBaseResponse<T>(url, data, config)` |
109 | | -- `delete<T>(url, config)` |
110 | | -- `deleteWithoutBaseResponse<T>(url, config)` |
111 | | -- `patch<T>(url, data, config)` |
112 | | -- `patchWithoutBaseResponse<T>(url, data, config)` |
| 151 | +如有问题或建议,欢迎提 issue 或 PR。 |
113 | 152 |
|
114 | | -所有被 `BaseResponse` 包装的方法,当请求失败时,会返回一个 `BaseResponse` 对象,其中 `code` 通常不为 200 或 201 等成功状态码,`data` 为 `null`,`msg` 包含错误信息。 |
| 153 | +- 仓库地址:https://github.com/ethanz-code/ethan-utils |
0 commit comments