如果你是在做 React Native 网络层封装 ,比较推荐把 Fetch / Axios、超时、请求/响应拦截、统一错误处理、Token 刷新这些能力集中到一个 request 层,而不是散落在业务代码里。
1. Fetch vs Axios
| 能力 | Fetch | Axios |
|---|---|---|
| RN 原生支持 | ✅ | 需要安装 |
| 超时 | ❌ 需要自己实现 | ✅ timeout |
| 请求拦截器 | ❌ | ✅ |
| 响应拦截器 | ❌ | ✅ |
| JSON 处理 | 手动 response.json() |
自动 |
| HTTP 错误处理 | 4xx/5xx 不自动 reject | 默认 reject |
| Token 注入 | 手动 | 拦截器 |
| Token 刷新 | 手动实现 | 拦截器中实现 |
| 取消请求 | AbortController |
AbortController |
| 复杂项目 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
如果是中大型 RN 项目,我更推荐 Axios。
2. Axios 基础封装
安装:
npm install axios
例如:
css
src/
├── api/
│ ├── client.ts
│ ├── user.ts
│ └── index.ts
├── storage/
│ └── token.ts
└── types/
└── api.ts
client.ts:
php
import axios from 'axios';
export const http = axios.create({
baseURL: 'https://api.example.com',
timeout: 15_000,
headers: {
'Content-Type': 'application/json',
},
});
业务层:
typescript
import { http } from './client';
export const getUser = (id: string) => {
return http.get(`/users/${id}`);
};
这样业务代码不需要关心 Axios 配置。
3. 请求拦截器
最常见的用途是自动加入 Token。
ini
http.interceptors.request.use(
async config => {
const token = await getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
},
);
于是:
csharp
http.get('/profile');
实际发送:
sql
GET /profile
Authorization: Bearer xxxxx
业务层不需要每次手动传 Token。
4. 响应拦截器
建议统一处理 HTTP 错误:
go
http.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response) {
const { status } = error.response;
switch (status) {
case 401:
// Token 失效
break;
case 403:
// 无权限
break;
case 404:
// API 不存在
break;
case 500:
// 服务端错误
break;
}
} else if (error.request) {
// 请求发出,但没有收到响应
} else {
// 请求配置阶段发生错误
}
return Promise.reject(error);
},
);
这里建议不要直接在拦截器里 Toast 所有错误。
例如:
dart
// ❌ 不推荐
Toast.show('请求失败');
因为有些接口的错误应该由页面自己处理。
更好的方式是:
arduino
throw new ApiError(...)
然后由业务层决定 UI 怎么表现。
5. Fetch 如何实现超时
Fetch 最大的问题之一就是没有 Axios 那么方便的 timeout。
现代 RN 可以使用 AbortController:
javascript
export async function request(
url: string,
options: RequestInit = {},
timeout = 15000,
) {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} finally {
clearTimeout(timer);
}
}
调用:
ini
const user = await request('/users/123');
15 秒没有完成,就会触发 abort()。
6. Axios 超时更简单
Axios:
ini
const http = axios.create({
baseURL: API_URL,
timeout: 15000,
});
超时后:
lua
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
console.log('请求超时');
}
}
因此 Axios 在这一点上明显更省事。
7. 真正项目里最重要的是 Token 刷新
例如:
css
请求 A ──→ 401
请求 B ──→ 401
请求 C ──→ 401
↓
refresh token
↓
新 access token
↓
A / B / C 重新请求
不要让 A、B、C 同时刷新 Token。
应该做成:
typescript
let isRefreshing = false;
let pendingRequests: Array<{
resolve: (token: string) => void;
reject: (error: unknown) => void;
}> = [];
第一个请求负责 refresh:
ini
if (!isRefreshing) {
isRefreshing = true;
try {
const token = await refreshToken();
pendingRequests.forEach(item => {
item.resolve(token);
});
pendingRequests = [];
} catch (error) {
pendingRequests.forEach(item => {
item.reject(error);
});
pendingRequests = [];
} finally {
isRefreshing = false;
}
}
后面的请求进入队列等待。
这是 RN Axios 封装里非常值得单独设计的一块。
8. 推荐的最终架构
我比较推荐:
vbscript
UI / Screen
│
▼
React Query
│
▼
API Service
│
▼
Axios Client
│
├── Request Interceptor
│ └── Access Token
│
├── Response Interceptor
│ ├── 统一错误
│ ├── 401
│ └── Token Refresh
│
└── Timeout
例如:
csharp
// user.ts
export async function getUser() {
const { data } = await http.get<User>('/user');
return data;
}
页面:
php
const { data, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: getUser,
});
这样就形成了:
UI 不关心 Axios,API 不关心 Token,Axios 不关心页面 UI。
这是比较干净的 RN 网络层分层方式。
如果你的项目是 RN + TypeScript + React Query ,我建议直接做成一个完整的 Axios Client:超时 + Token 注入 + 401 自动刷新 + 并发请求队列 + 统一 ApiError + 请求取消,这套基本可以直接用于生产项目。