Автоматическое обновление данных

Одной из ключевых возможностей TanStack Query является автоматическая синхронизация серверного состояния. Библиотека самостоятельно определяет моменты, когда данные могут устареть, и инициирует повторные запросы без ручного вмешательства.

Автоматическое обновление данных решает несколько задач:

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

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


staleTime и понятие устаревших данных

Центральным понятием автоматического обновления является состояние stale.

По умолчанию любые данные считаются устаревшими сразу после загрузки.

useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers
})

После успешного выполнения запроса:

  • данные попадают в кеш;
  • компонент получает данные;
  • запрос становится stale.

Это не означает мгновенный повторный запрос. Статус stale лишь говорит системе, что данные можно обновлять при наступлении определённых событий.

Для управления этим используется staleTime.

useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    staleTime: 1000 * 60
})

В данном случае данные считаются свежими одну минуту.

Пока данные fresh:

  • повторный mount компонента не вызовет запрос;
  • refetch при фокусе окна не произойдёт;
  • refetch при reconnect не произойдёт.

После истечения времени данные становятся stale.


Поведение staleTime

staleTime: 0

Стандартное поведение.

staleTime: 0

Данные устаревают мгновенно.

Подходит для:

  • frequently changing API;
  • realtime dashboards;
  • чатов;
  • финансовых данных.

Большой staleTime

staleTime: 1000 * 60 * 10

Данные считаются свежими 10 минут.

Подходит для:

  • справочников;
  • списков стран;
  • настроек;
  • редко изменяемых данных.

Infinity

staleTime: Infinity

Данные никогда не становятся stale автоматически.

Повторное обновление возможно только:

  • вручную;
  • через invalidateQueries;
  • через refetch;
  • после удаления кеша.

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

Одним из самых полезных механизмов является refetchOnWindowFocus.

Когда пользователь возвращается во вкладку браузера, TanStack Query может автоматически обновить данные.

useQuery({
    queryKey: ['notifications'],
    queryFn: fetchNotifications,
    refetchOnWindowFocus: true
})

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

  1. пользователь переключается на другую вкладку;
  2. данные становятся потенциально устаревшими;
  3. пользователь возвращается;
  4. TanStack Query запускает refetch.

Это особенно полезно для:

  • уведомлений;
  • сообщений;
  • административных панелей;
  • статистики;
  • мониторинговых систем.

Отключение refetch при фокусе

useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    refetchOnWindowFocus: false
})

Полезно, если:

  • запрос тяжёлый;
  • данные редко меняются;
  • обновление создаёт нагрузку;
  • требуется полный контроль.

Поведение с staleTime

Важно понимать взаимодействие со staleTime.

useQuery({
    queryKey: ['profile'],
    queryFn: fetchProfile,
    staleTime: 1000 * 60 * 5,
    refetchOnWindowFocus: true
})

Если пользователь вернулся во вкладку через:

  • 2 минуты — refetch не произойдёт;
  • 6 минут — данные обновятся.

Потому что refetch выполняется только для stale-запросов.


refetchOnMount

Этот параметр определяет поведение при повторном mount компонента.

useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    refetchOnMount: true
})

Когда компонент монтируется повторно:

  • TanStack Query проверяет stale status;
  • при необходимости запускает refetch.

Значения refetchOnMount

true

Поведение по умолчанию.

Refetch выполняется только если данные stale.


false

refetchOnMount: false

Повторный mount не вызывает refetch.


“always”

refetchOnMount: 'always'

Запрос выполняется всегда при mount.

Даже если данные fresh.

Полезно для:

  • критически важных данных;
  • страниц с высокой актуальностью;
  • realtime интерфейсов.

refetchOnReconnect

TanStack Query отслеживает состояние сети.

После восстановления соединения библиотека может автоматически обновить данные.

useQuery({
    queryKey: ['messages'],
    queryFn: fetchMessages,
    refetchOnReconnect: true
})

Сценарий работы:

  1. устройство теряет интернет;
  2. запросы перестают выполняться;
  3. сеть восстанавливается;
  4. stale-запросы обновляются автоматически.

Отключение reconnect refetch

useQuery({
    queryKey: ['products'],
    queryFn: fetchProducts,
    refetchOnReconnect: false
})

Иногда полезно для:

  • медленных API;
  • мобильных сетей;
  • дорогостоящих запросов.

Интервальное обновление данных

TanStack Query поддерживает polling через refetchInterval.

useQuery({
    queryKey: ['stats'],
    queryFn: fetchStats,
    refetchInterval: 5000
})

Запрос будет повторяться каждые 5 секунд.


Как работает polling

При активном query:

refetchInterval: 5000

TanStack Query:

  • создаёт внутренний таймер;
  • вызывает queryFn;
  • обновляет кеш;
  • уведомляет подписчиков;
  • повторяет цикл.

Polling и неактивные вкладки

По умолчанию polling останавливается в неактивной вкладке браузера.

Это экономит:

  • батарею;
  • CPU;
  • сетевой трафик.

refetchIntervalInBackground

Для продолжения polling в фоне используется:

useQuery({
    queryKey: ['stats'],
    queryFn: fetchStats,
    refetchInterval: 5000,
    refetchIntervalInBackground: true
})

Теперь refetch продолжится даже при свернутом окне.


Динамический refetchInterval

Интервал может вычисляться динамически.

useQuery({
    queryKey: ['job'],
    queryFn: fetchJob,
    refetchInterval: (data) => {
        if (data?.status === 'finished') {
            return false
        }

        return 2000
    }
})

Пока задача выполняется:

  • polling продолжается каждые 2 секунды.

После завершения:

  • polling автоматически отключается.

Остановка polling

Через false

refetchInterval: false

Polling отключён.


Через enabled

useQuery({
    queryKey: ['stats'],
    queryFn: fetchStats,
    enabled: isActive,
    refetchInterval: 3000
})

При enabled: false:

  • polling останавливается;
  • запросы не выполняются.

Ручной refetch

Автоматическое обновление можно комбинировать с ручным.

const { data, refetch } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers
})

Вызов:

await refetch()

принудительно выполняет запрос.


invalidateQueries и автоматический refetch

Одним из важнейших механизмов обновления является invalidation.

queryClient.invalidateQueries({
    queryKey: ['users']
})

Invalidate:

  • помечает query как stale;
  • инициирует refetch активных запросов.

Автоматическое обновление после mutation

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

const mutation = useMutation({
    mutationFn: createPost,
    onSuccess: () => {
        queryClient.invalidateQueries({
            queryKey: ['posts']
        })
    }
})

После создания записи:

  1. кеш помечается stale;
  2. список posts обновляется автоматически;
  3. UI синхронизируется с сервером.

Refetch активных и неактивных запросов

По умолчанию refetch выполняется только для active queries.

Неактивные query:

  • получают статус stale;
  • обновятся позже при активации.

Принудительный refetch inactive queries

queryClient.refetchQueries({
    queryKey: ['users'],
    type: 'inactive'
})

или:

queryClient.refetchQueries({
    queryKey: ['users'],
    type: 'all'
})

Автоматическая дедупликация запросов

TanStack Query предотвращает дублирование запросов.

Если одновременно несколько компонентов используют:

['users']

будет выполнен только один HTTP-запрос.

Остальные компоненты подпишутся на тот же Promise.


Сценарий работы deduplication

Допустим:

<UserList />
<UserSidebar />
<UserStats />

Все используют:

queryKey: ['users']

TanStack Query:

  • создаёт один запрос;
  • кеширует результат;
  • распределяет данные между подписчиками.

Это особенно важно при автоматическом refetch.


Background fetching

Во время автоматического обновления старые данные остаются доступными.

Интерфейс не очищается.

const {
    data,
    isFetching
} = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts
})

Во время background refetch:

  • data содержит старые данные;
  • isFetching === true.

isLoading vs isFetching

isLoading

isLoading

Истинен только при первой загрузке.


isFetching

isFetching

Истинен при любом запросе:

  • initial fetch;
  • background refetch;
  • polling;
  • manual refetch.

Индикаторы фонового обновления

Пример:

function Posts() {
    const {
        data,
        isFetching
    } = useQuery({
        queryKey: ['posts'],
        queryFn: fetchPosts,
        refetchInterval: 10000
    })

    return (
        <div>
            {isFetching && (
                <span>Обновление...</span>
            )}

            {data.map(post => (
                <div key={post.id}>
                    {post.title}
                </div>
            ))}
        </div>
    )
}

Интерфейс остаётся стабильным даже во время обновления.


networkMode

TanStack Query поддерживает различные режимы работы сети.

useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    networkMode: 'online'
})

Режимы networkMode

online

Стандартное поведение.

Запросы зависят от состояния сети.


always

networkMode: 'always'

Запросы выполняются даже при offline status.

Полезно для:

  • custom network layers;
  • electron applications;
  • нестандартных transport mechanisms.

offlineFirst

networkMode: 'offlineFirst'

Оптимизировано для offline-capable приложений.


focusManager

Внутренний менеджер фокуса управляет:

  • refetchOnWindowFocus;
  • background activity;
  • visibility detection.

Можно переопределить поведение.

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

focusManager.setEventListener((handleFocus) => {
    window.addEventListener('visibilitychange', handleFocus)

    return () => {
        window.removeEventListener(
            'visibilitychange',
            handleFocus
        )
    }
})

onlineManager

Менеджер сети управляет reconnect logic.

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

onlineManager.setEventListener((setOnline) => {
    window.addEventListener('online', () => {
        setOnline(true)
    })

    window.addEventListener('offline', () => {
        setOnline(false)
    })

    return () => {
        window.removeEventListener('online', setOnline)
        window.removeEventListener('offline', setOnline)
    }
})

Глобальные настройки автоматического обновления

Настройки можно вынести в QueryClient.

const queryClient = new QueryClient({
    defaultOptions: {
        queries: {
            staleTime: 1000 * 60,
            refetchOnWindowFocus: false,
            refetchOnReconnect: true,
            refetchOnMount: true
        }
    }
})

Теперь все query наследуют эти параметры.


Практическая стратегия настройки

Редко изменяемые данные

staleTime: 1000 * 60 * 30

Пользовательский профиль

staleTime: 1000 * 60 * 5

Финансовые данные

refetchInterval: 1000

Административные панели

refetchOnWindowFocus: true

Мобильные приложения

refetchOnReconnect: true

Проблемы чрезмерного refetch

Слишком агрессивное обновление может привести к:

  • лишней нагрузке;
  • повышенному трафику;
  • постоянным rerender;
  • перегрузке API.

Пример плохой конфигурации:

useQuery({
    queryKey: ['stats'],
    queryFn: fetchStats,
    staleTime: 0,
    refetchInterval: 500
})

Каждые полсекунды будет выполняться новый запрос.


Оптимизированная конфигурация

useQuery({
    queryKey: ['stats'],
    queryFn: fetchStats,
    staleTime: 5000,
    refetchInterval: 10000,
    refetchOnWindowFocus: false
})

Такой подход уменьшает количество сетевых операций и сохраняет актуальность данных.


Комбинирование механизмов обновления

На практике механизмы обычно работают вместе.

useQuery({
    queryKey: ['dashboard'],
    queryFn: fetchDashboard,
    staleTime: 1000 * 30,
    refetchOnWindowFocus: true,
    refetchOnReconnect: true,
    refetchInterval: 60000
})

Поведение:

  • данные fresh 30 секунд;
  • при возврате во вкладку возможен refetch;
  • после reconnect выполняется обновление;
  • раз в минуту запускается polling.

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