网络状态感知
TanStack Query 的 onlineManager 可以感知网络状态,在离线时暂停请求、恢复连接后自动重试。
#type / howto
#status / evergreen
#tech / dev / frontend
#resource / react
[!info] related notes
- 前置: TanStack Query 服务端状态
- 所属 MOC: TanStack Query 知识地图
- 关联: 错误处理与重试策略
- 关联: 请求取消与 AbortController
网络状态感知
核心问题
用户在地铁里用你的应用,网络断断续续:
打开页面 → 请求发出 → 网络断开 → 请求失败
用户切到其他 app → 网络恢复 → 切回来
期望:自动重新请求,显示最新数据
onlineManager
TanStack Query 通过 onlineManager 监听网络状态:
import { onlineManager } from '@tanstack/react-query';
// 默认行为:监听 window 的 online/offline 事件
// 网络恢复时,自动重新请求所有 stale 的 query
自定义网络检测
默认的 navigator.onLine 不够可靠(它只检测本地网络连接,不检测互联网可达)。可以自定义:
import { onlineManager } from '@tanstack/react-query';
// 自定义网络检测:实际 ping 服务器
onlineManager.setEventListener((setOnline) => {
const interval = setInterval(async () => {
try {
await fetch('/api/health', { method: 'HEAD' });
setOnline(true);
} catch {
setOnline(false);
}
}, 5000);
return () => clearInterval(interval);
});
手动控制
import { onlineManager } from '@tanstack/react-query';
// 手动设置在线状态
onlineManager.setOnline(true);
onlineManager.setOnline(false);
// 获取当前状态
const isOnline = onlineManager.isOnline();
networkMode
控制 query 在不同网络状态下的行为:
useQuery({
queryKey: ['data'],
queryFn: fetchData,
networkMode: 'online', // 默认
});
三种模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
'online' | 离线时不请求,恢复后重试 | 大多数场景(默认) |
'always' | 不管网络状态都请求 | 本地 API、Service Worker |
'offlineFirst' | 先用缓存,后台尝试更新 | 离线优先应用 |
// 离线优先:先显示缓存,后台静默更新
useQuery({
queryKey: ['articles'],
queryFn: fetchArticles,
networkMode: 'offlineFirst',
staleTime: 5 * 60 * 1000,
});
离线时的行为
默认(networkMode: ‘online’)
网络断开
→ 新的 useQuery 挂载:不发请求,保持 isLoading 状态
→ 已有的 stale query:不自动 refetch
→ mutation:进入 paused 状态,恢复后自动重发
网络恢复
→ 所有 stale 的 query 自动 refetch
→ paused 的 mutation 自动重发
离线时显示缓存
const { data, isLoading, fetchStatus } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
});
// isLoading: 没有数据且正在加载
// fetchStatus: 'fetching' | 'paused' | 'idle'
if (isLoading && fetchStatus === 'paused') {
// 离线且没有缓存
return <OfflineMessage />;
}
if (data) {
return <DataView data={data} />;
}
离线 Mutation
mutation 在离线时不会失败,而是进入 paused 状态:
const mutation = useMutation({
mutationFn: createPost,
onMutate: async (newPost) => {
// 乐观更新:离线时 UI 立即反映
await queryClient.cancelQueries({ queryKey: ['posts'] });
const previous = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], old => [...old, newPost]);
return { previous };
},
});
// mutation.isPaused === true 表示离线排队中
if (mutation.isPaused) {
return <div>离线中,恢复网络后自动提交</div>;
}
恢复后的重发顺序
离线期间的 mutation 队列:
1. createPost(A)
2. updatePost(B)
3. deletePost(C)
网络恢复后,按顺序依次重发。
实战:离线友好的应用
function App() {
return (
<QueryClientProvider client={queryClient}>
<OnlineStatusIndicator />
<Router />
</QueryClientProvider );
}
function OnlineStatusIndicator() {
const isOnline = onlineManager.useIsOnline(); // v5 hook
if (!isOnline) {
return (
<div className="offline-banner">
离线模式 — 数据可能不是最新的
</div>
);
}
return null;
}
常见错误
以为 navigator.onLine 可靠
// ❌ navigator.onLine 只检测本地连接
// 连着 WiFi 但没网,它仍然返回 true
// ✅ 自定义检测
onlineManager.setEventListener((setOnline) => {
// 实际检测互联网可达性
});
离线时不做乐观更新
// ❌ 离线时 UI 无反馈
mutation.mutate(newData);
// 用户点了按钮但什么都没发生
// ✅ 乐观更新 + 离线提示
mutation.mutate(newData, {
onMutate: async () => {
// 乐观更新 UI
},
});
if (mutation.isPaused) {
toast('已保存,恢复网络后自动同步');
}