Polling данных

Polling (периодическое опросное обновление данных) в TanStack Query представляет собой механизм автоматического повторного запроса к серверу через заданные интервалы времени. Этот подход используется в сценариях, где данные изменяются регулярно и требуется поддерживать интерфейс в актуальном состоянии без ручного обновления страницы или действий пользователя.

В отличие от событийных моделей (WebSocket, Server-Sent Events), polling основан на циклическом выполнении запросов и полностью управляется клиентской логикой TanStack Query через конфигурацию query.


Базовый механизм refetchInterval

Основной инструмент для реализации polling — параметр refetchInterval. Он задаёт интервал в миллисекундах, через который запрос будет автоматически повторяться.

import { useQuery } from '@tanstack/react-query'

function useNotifications() {
  return useQuery({
    queryKey: ['notifications'],
    queryFn: fetchNotifications,
    refetchInterval: 5000
  })
}

В данном случае запрос fetchNotifications будет выполняться каждые 5 секунд, пока компонент, использующий query, находится в активном состоянии.

Ключевая особенность поведения:

  • интервал запускается после успешного получения данных
  • новый запрос не блокирует UI
  • предыдущий запрос должен завершиться перед следующим (по умолчанию)

Динамическое управление интервалом

refetchInterval может быть функцией, что позволяет управлять polling динамически, учитывая состояние данных или приложения.

useQuery({
  queryKey: ['orders'],
  queryFn: fetchOrders,
  refetchInterval: (data) => {
    if (!data) return 1000
    if (data.status === 'processing') return 2000
    if (data.status === 'completed') return false
    return 5000
  }
})

Логика:

  • отсутствие данных увеличивает частоту запросов
  • активная обработка требует более частого обновления
  • завершённое состояние полностью отключает polling

Возврат false отключает автоматическое обновление.


Условное включение polling через enabled

Polling может быть зависим от внешних условий. Параметр enabled управляет активацией запроса целиком, включая его автоматическое повторение.

useQuery({
  queryKey: ['messages', chatId],
  queryFn: () => fetchMessages(chatId),
  enabled: !!chatId,
  refetchInterval: 3000
})

Если chatId отсутствует, запрос полностью деактивируется, включая polling.


Поведение при фокусе окна

TanStack Query по умолчанию интегрирует refetch при возврате фокуса окна. Это влияет на polling, так как может приводить к дополнительным запросам.

Поведение контролируется через:

  • refetchOnWindowFocus
  • глобальные настройки QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: true
    }
  }
})

При включённом polling и активном refetch on focus возможны «всплески» запросов при переключении вкладок.


refetchIntervalInBackground

По умолчанию TanStack Query приостанавливает polling, когда вкладка находится в фоне. Это оптимизация для экономии ресурсов.

Поведение управляется параметром:

useQuery({
  queryKey: ['prices'],
  queryFn: fetchPrices,
  refetchInterval: 2000,
  refetchIntervalInBackground: true
})

Если значение true, polling продолжается даже при неактивной вкладке.

Сценарии использования:

  • торговые панели
  • мониторинг систем
  • real-time аналитика

Сценарии, где лучше оставить false:

  • обычные CRUD-интерфейсы
  • списки без критичной актуальности
  • мобильные устройства с ограниченными ресурсами

Связь polling и staleTime

staleTime влияет на то, считается ли данные устаревшими, но не отключает polling напрямую. Однако он меняет поведение refetch при монтировании и фокусе.

useQuery({
  queryKey: ['profile'],
  queryFn: fetchProfile,
  staleTime: 10000,
  refetchInterval: 3000
})

Даже если данные считаются свежими в рамках staleTime, polling продолжает выполняться.

Разделение логики:

  • staleTime управляет «свежестью» данных
  • refetchInterval управляет циклом обновления

Поведение при наличии активных мутаций

Polling может пересекаться с мутациями (useMutation), что приводит к потенциальным гонкам данных.

Типичный сценарий:

  • polling обновляет список
  • пользователь выполняет mutation
  • refetch перезаписывает локальные изменения

Для минимизации конфликтов используются:

  • queryClient.invalidateQueries
  • временное отключение polling
  • оптимистичные обновления

Пример отключения polling на время мутации:

const query = useQuery({
  queryKey: ['tasks'],
  queryFn: fetchTasks,
  refetchInterval: 3000
})

const mutation = useMutation({
  mutationFn: updateTask,
  onMutate: async () => {
    await queryClient.cancelQueries(['tasks'])
  },
  onSettled: () => {
    queryClient.invalidateQueries(['tasks'])
  }
})

Управление через refetchInterval: функции состояния

Функциональная форма refetchInterval позволяет учитывать не только данные, но и состояние query.

useQuery({
  queryKey: ['sync'],
  queryFn: fetchSyncState,
  refetchInterval: (data, query) => {
    if (query.state.status === 'error') return 1000
    if (query.state.fetchStatus === 'fetching') return false
    return 5000
  }
})

Здесь учитываются:

  • статус запроса
  • состояние загрузки
  • бизнес-логика данных

Отключение polling

Polling можно полностью отключить несколькими способами:

useQuery({
  queryKey: ['logs'],
  queryFn: fetchLogs,
  refetchInterval: false
})

Или динамически:

refetchInterval: shouldPoll ? 2000 : false

Также отключение возможно через:

  • enabled: false
  • условную логику компонента
  • удаление query из дерева

Влияние сетевого состояния

TanStack Query учитывает состояние сети браузера. При потере соединения polling автоматически приостанавливается.

После восстановления:

  • запросы возобновляются
  • выполняется refetch актуального состояния
useQuery({
  queryKey: ['dashboard'],
  queryFn: fetchDashboard,
  refetchInterval: 4000,
  networkMode: 'online'
})

Режимы:

  • online — запросы только при активной сети
  • always — попытка выполнения даже без сети (с fallback логикой)
  • offlineFirst — приоритет локального кеша

Оптимизация polling в больших приложениях

При масштабировании системы с множеством polling-запросов возникают проблемы производительности:

  • перегрузка API
  • лишние сетевые запросы
  • конкуренция за ресурсы браузера

Подходы оптимизации:

  • объединение запросов в один queryKey
  • увеличение staleTime
  • адаптивный refetchInterval
  • отключение polling вне viewport (через enabled)
  • использование серверной агрегации данных

Пример адаптивного polling:

useQuery({
  queryKey: ['metrics'],
  queryFn: fetchMetrics,
  refetchInterval: (data) => {
    if (!data) return 1000
    return data.load > 80 ? 2000 : 10000
  }
})

Комбинация polling и кеширования

TanStack Query использует кеш как базовый слой, а polling обновляет его инкрементально.

Сценарий:

  • первый запрос кладёт данные в кеш
  • polling обновляет только при изменении ответа
  • компоненты подписанные на query получают обновления автоматически

Это позволяет:

  • минимизировать ручное управление состоянием
  • исключить глобальные сторы для данных сервера
  • поддерживать синхронность интерфейса без дополнительных эффектов