Polling и рефетчинг

Polling в RTK Query представляет собой механизм периодического автоматического обновления данных по заранее заданному интервалу времени. Он реализуется на уровне подписки на query и позволяет поддерживать состояние клиента синхронизированным с сервером без ручного вмешательства.

Ключевая особенность polling заключается в том, что он работает только пока существует активная подписка на запрос. Как только последний компонент, использующий query, размонтируется, polling автоматически прекращается. Это напрямую связано с системой кэширования и подписок RTK Query.

Базовая активация polling через pollingInterval

Основной способ включения polling — передача параметра pollingInterval в хук useQuery.

const { data, error, isLoading } = useGetUsersQuery(undefined, {
  pollingInterval: 5000
});

Значение задаётся в миллисекундах. В данном примере запрос будет повторяться каждые 5 секунд.

Поведение:

  • интервал отсчитывается с момента последнего успешного или неуспешного запроса;
  • новый запрос инициируется автоматически;
  • предыдущий результат перезаписывается в кэш;
  • все компоненты, подписанные на тот же endpoint с теми же аргументами, разделяют один polling процесс.

Условия остановки polling

Polling автоматически прекращается при следующих условиях:

  • размонтирован последний подписанный компонент;
  • параметр skip становится true;
  • явно изменяется аргумент запроса (создаётся новая подписка);
  • вызывается unsubscribe на уровне низкоуровневого API.
const query = useGetUsersQuery(undefined, {
  pollingInterval: 3000,
  skip: isPaused
});

В данном случае polling динамически управляется через состояние isPaused.

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

pollingInterval может изменяться во время жизненного цикла компонента. Это приводит к пересозданию таймера.

const interval = isFastMode ? 2000 : 10000;

useGetUsersQuery(undefined, {
  pollingInterval: interval
});

RTK Query корректно пересчитывает интервал без необходимости ручного сброса подписки.


Рефетчинг (refetching)

Рефетчинг в RTK Query — это механизм принудительного или условного повторного запроса данных для обновления кэша.

В отличие от polling, который работает автоматически по таймеру, refetching чаще связан с событиями: фокус окна, восстановление соединения, ручной вызов или инвалидация данных.

Ручной refetch через хук

Каждый query-хук возвращает функцию refetch, которая инициирует повторный запрос независимо от кэша.

const { data, refetch } = useGetUsersQuery();

<button onCl ick={() => refetch()}>
  Обновить данные
</button>

Особенности поведения:

  • игнорирует дедупликацию запросов;
  • не зависит от cacheTime и keepUnusedDataFor;
  • всегда инициирует сетевой запрос;
  • обновляет cache entry после успешного ответа.

Отличие refetch от повторной подписки

Повторное использование useQuery с теми же аргументами не всегда вызывает сетевой запрос, так как RTK Query использует кэширование и дедупликацию.

Refetch же:

  • обходит оптимизации повторного использования;
  • работает как явный сигнал «обновить данные сейчас»;
  • не создаёт новую подписку, а использует существующую.

Автоматический refetch при фокусе окна

RTK Query поддерживает автоматическое обновление данных при возвращении фокуса в окно браузера.

Это управляется параметром:

refetchOnFocus: true

Пример:

useGetUsersQuery(undefined, {
  refetchOnFocus: true
});

Механика работы:

  • слушается событие visibilitychange;
  • при возвращении страницы в активное состояние инициируется refetch;
  • запрос выполняется только при наличии активной подписки;
  • дублирующие запросы дедуплицируются.

Глобальная настройка refetchOnFocus

В api можно задать поведение по умолчанию:

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: (builder) => ({
    getUsers: builder.query({
      query: () => "/users"
    })
  }),
  refetchOnFocus: true
});

Это позволяет централизованно управлять стратегией обновления данных.


Refetch при восстановлении соединения

RTK Query может автоматически повторять запросы при восстановлении интернет-соединения.

useGetUsersQuery(undefined, {
  refetchOnReconnect: true
});

Поведение:

  • отслеживается событие online;
  • при переходе из offline в online выполняется повторный запрос;
  • используется актуальная подписка;
  • предотвращается работа с устаревшими данными после разрыва сети.

Комбинация refetchOnFocus и refetchOnReconnect часто используется для обеспечения актуальности данных в SPA.


Взаимодействие polling и refetching

Polling и refetching не конфликтуют, но могут пересекаться по времени выполнения запросов.

Сценарии взаимодействия:

  1. Polling активен, происходит refetch вручную → ручной запрос выполняется независимо, polling продолжает работать по таймеру

  2. Refetch совпадает с polling-интервалом → возможна дедупликация, если запрос идентичен и уже выполняется

  3. refetchOnFocus с polling → после возврата фокуса выполняется дополнительный запрос, затем продолжается polling

Важный момент дедупликации

RTK Query избегает одновременного выполнения идентичных запросов:

  • одинаковый endpoint + аргументы;
  • активный pending-запрос уже существует;

В таких случаях второй запрос может быть объединён с первым.


forceRefetch и управление кешем

В некоторых случаях требуется игнорировать кэш даже при совпадении данных.

dispatch(
  api.endpoints.getUsers.initiate(undefined, {
    forceRefetch: true
  })
);

Это поведение:

  • заставляет RTK Query выполнить сетевой запрос;
  • не использует cached result;
  • обновляет store независимо от свежести данных.

Условный refetch через selectFromResult

selectFromResult позволяет управлять зависимостью от части состояния и триггерить повторные запросы через изменение селектора.

const { data } = useGetUsersQuery(undefined, {
  selectFromResult: ({ data }) => ({
    data: data?.filter(user => user.active)
  })
});

Хотя это не прямой механизм refetch, изменение селектора влияет на подписку и может косвенно приводить к обновлению данных при изменении аргументов или состояния кэша.


Управление жизненным циклом polling

Polling тесно связан с жизненным циклом подписки:

  • mount компонента → старт polling (если задан);
  • update аргументов → пересоздание polling;
  • unmount последнего subscriber → остановка polling.

RTK Query не использует глобальные таймеры без активных подписок, что снижает нагрузку на приложение.


Polling в условиях нескольких подписчиков

Если один endpoint используется в нескольких компонентах:

useGetUsersQuery(undefined, { pollingInterval: 5000 });
useGetUsersQuery(undefined, { pollingInterval: 5000 });

RTK Query:

  • создаёт единую подписку;
  • использует один polling-таймер;
  • синхронизирует результат между компонентами.

Если интервалы отличаются:

useGetUsersQuery(undefined, { pollingInterval: 2000 });
useGetUsersQuery(undefined, { pollingInterval: 10000 });

Приоритет получает наименьший интервал (наиболее частое обновление), так как он обеспечивает более свежие данные для всех подписчиков.


Прерывание polling через skip

skip полностью отключает запрос и polling:

useGetUsersQuery(undefined, {
  skip: !isAuthorized,
  pollingInterval: 5000
});

При skip = true:

  • запрос не выполняется;
  • polling не запускается;
  • кэш может оставаться доступным, но не обновляется.

Практика комбинирования стратегий обновления

RTK Query позволяет комбинировать несколько механизмов актуализации данных:

  • pollingInterval для периодического обновления;
  • refetchOnFocus для пользовательской активности;
  • refetchOnReconnect для сетевых изменений;
  • refetch для ручного контроля.

Типичная конфигурация:

useGetUsersQuery(undefined, {
  pollingInterval: 10000,
  refetchOnFocus: true,
  refetchOnReconnect: true
});

Такой набор создаёт модель, при которой данные обновляются:

  • каждые 10 секунд при активной вкладке;
  • при возврате пользователя в приложение;
  • при восстановлении соединения;
  • при ручном вызове refetch.

Поведение кэша при постоянном обновлении

При активном polling и refetching:

  • cache всегда содержит последний успешный результат;
  • промежуточные ошибки не перезаписывают валидные данные;
  • RTK Query сохраняет предыдущий data до получения нового ответа;
  • состояние isFetching отражает текущую активность запроса.

Это позволяет UI оставаться стабильным даже при частых обновлениях данных.