RN 网络请求与 Web 的差异
React Native 内置了 fetch API(与浏览器 fetch 相同),也支持 Axios。与 Web 开发最大的不同是:没有 CORS 限制。原生 App 发出的网络请求不受同源策略约束,这既是方便之处,也意味着你需要在服务端做好权限验证。
另一个差异是证书验证。RN 会严格验证 HTTPS 证书,自签名证书需要额外配置才能在真机上使用。开发环境推荐用 mkcert 生成受信任的本地证书。
还有网络状态问题:手机用户经常在 WiFi 和移动网络之间切换,甚至进入无信号区域。一个好的移动应用必须优雅处理网络中断场景,这是 Web 开发较少面对的挑战。
核心名词解释
fetch
RN 内置的网络请求 API,与浏览器 fetch 兼容。适合简单请求,复杂项目推荐 Axios(更好的拦截器、错误处理、TypeScript 支持)。
Axios
功能丰富的 HTTP 客户端库,支持请求/响应拦截器、自动 JSON 转换、超时设置、取消请求。配合 React Query 使用是生产项目的标准组合。
React Query
服务端状态管理,提供智能缓存(避免重复请求)、后台刷新(Stale-While-Revalidate)、乐观更新、请求去重等功能。
AsyncStorage
React Native 官方异步键值存储,基于文件系统。数据以 JSON 字符串形式存储,存取都是异步操作。适合存储用户偏好、轻量数据,不适合存储大量数据。
MMKV
微信团队开源的高性能键值存储,基于内存映射文件(mmap)实现,读写速度比 AsyncStorage 快 30 倍,且支持同步操作。推荐作为 AsyncStorage 的替代品。
离线优先(Offline First)
设计模式:先展示本地缓存数据,后台尝试更新;写入操作在网络断开时排队,恢复后自动同步。让应用在无网络时依然可用。
Optimistic Update(乐观更新)
操作后立即更新 UI(假设成功),同时发送网络请求。若请求失败则回滚 UI 并提示用户。点赞、关注等操作的标准实现方式。
请求链路架构
完整请求链路(组件 → React Query → Axios → API)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
UI 组件
│
│ useQuery({ queryKey: ['posts'], queryFn: ... })
▼
React Query Cache
│ Cache Hit? ──Yes──▶ 立即返回缓存数据(stale)
│ 同时在后台发起请求(revalidate)
│ Cache Miss?
▼
QueryFn(你定义的函数)
│ api.getPosts()
▼
Axios Instance
│ 请求拦截器:自动附加 Authorization: Bearer
│ 请求拦截器:添加 Accept-Language、设备信息 Header
▼
API Server
│ 响应 200: data
│ 响应 401: 刷新 Token 并重试
│ 响应 500: 触发 retry(最多2次)
▼
Axios 响应拦截器
│ 统一解包 data.data
│ 统一处理错误 Toast
▼
React Query Cache 更新
│ 通知所有订阅该 queryKey 的组件重渲染
▼
UI 组件收到最新数据
Axios 封装
// lib/api.ts — Axios 实例封装
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { useAuthStore } from '../store/authStore';
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV();
export const api = axios.create({
baseURL: 'https://api.myapp.com/v1',
timeout: 10000, // 10秒超时
headers: { 'Content-Type': 'application/json' },
});
// 请求拦截器:自动附加 Token
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器:统一错误处理 + Token 刷新
api.interceptors.response.use(
response => response.data, // 自动解包 data
async (error: AxiosError) => {
const status = error.response?.status;
if (status === 401) {
// Token 过期,尝试刷新
try {
const refreshToken = storage.getString('refreshToken');
const { token } = await api.post('/auth/refresh', { refreshToken });
useAuthStore.getState().setToken(token);
// 重试原始请求
return api(error.config!);
} catch {
useAuthStore.getState().logout();
}
}
return Promise.reject(error);
}
);
AsyncStorage vs MMKV
AsyncStorage 是 RN 官方的本地存储方案,但它有明显的性能缺陷:所有操作都是异步的,且存储格式为纯文本 JSON,大数据量时解析开销显著。
MMKV 是微信团队开源的高性能方案,已被 React Native 社区广泛采用。它基于内存映射文件(mmap),读写都在内存中完成,速度极快,且支持同步操作(不需要 async/await)。
// AsyncStorage — 异步,慢
import AsyncStorage from '@react-native-async-storage/async-storage';
await AsyncStorage.setItem('theme', 'dark');
const theme = await AsyncStorage.getItem('theme');
// MMKV — 同步,快 30x
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('theme', 'dark'); // 同步!无需 await
const theme = storage.getString('theme'); // 同步!
storage.set('count', 42); // 支持 number 直接存储
const count = storage.getNumber('count'); // 100
// 多实例(不同命名空间)
const userStorage = new MMKV({ id: 'user' });
const cacheStorage = new MMKV({ id: 'cache' });
图片缓存与离线优先
// expo-image:官方推荐,内置磁盘/内存缓存
import { Image } from 'expo-image';
const blurhash = '|rF?hV%2WCj[ayj[a|j[az_NaeWBj@ayfRayfQfQM{M|azj[azf6fQfQIpWXofj[ayj[j[fQayWCoeoeaya}j[ayfQa{oLj?j[WVj[ayayj[fQoff7azayj[ayj[j[ayofayayayj[';
export function CachedImage({ uri }: { uri: string }) {
return (
<Image
source={{ uri }}
style={{ width: 300, height: 200 }}
placeholder={blurhash} // 模糊占位(加载前显示)
contentFit="cover"
transition={300} // 淡入过渡动画
cachePolicy="memory-disk" // 内存+磁盘双缓存
/>
);
}
避免的反模式
不要在
useEffect 里直接写 fetch 请求——这种方式没有缓存、没有错误重试、没有加载状态管理,是 React 生态中出了名的"初学者陷阱"。所有服务端数据获取都应该通过 React Query 的 useQuery 进行。
网络状态监听
使用
@react-native-community/netinfo 监听网络状态变化,在断网时禁用提交按钮、显示离线提示;网络恢复时自动触发 React Query 的 refetch(React Query 内置了此功能,配置 refetchOnReconnect: true 即可)。