网络状态感知

TanStack Query 的 onlineManager 可以感知网络状态,在离线时暂停请求、恢复连接后自动重试。

#type / howto #status / evergreen #tech / dev / frontend #resource / react

[!info] related notes

网络状态感知

核心问题

用户在地铁里用你的应用,网络断断续续:

打开页面 → 请求发出 → 网络断开 → 请求失败
用户切到其他 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('已保存,恢复网络后自动同步');
}
创建于 2026/7/3 更新于 2026/7/15