Дебаунсинг в контексте запросов TanStack Query применяется для ограничения частоты сетевых обращений при изменяющихся входных данных, чаще всего при поиске, фильтрации или динамическом вводе. Основная цель заключается в том, чтобы отложить выполнение запроса до момента, когда поток изменений стабилизируется, тем самым снижая нагрузку на сервер и предотвращая избыточные запросы.
В классическом сценарии поиска пользователь вводит текст, и каждое нажатие клавиши может триггерить изменение состояния. Без ограничения это приводит к цепочке запросов на каждый символ. TanStack Query сам по себе не навязывает механизм дебаунса, но предоставляет инструменты, с помощью которых подобное поведение реализуется на уровне ключей запросов, состояния и управления включением запроса.
Наиболее распространённый подход заключается в разделении «сырых» вводимых данных и значения, которое используется непосредственно в queryKey. Сырые данные обновляются мгновенно, тогда как «отложенное» значение изменяется с задержкой.
import { useState, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
function useDebouncedValue(value, delay) {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebounced(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debounced
}
function useSearch(query) {
const debouncedQuery = useDebouncedValue(query, 400)
return useQuery({
queryKey: ['search', debouncedQuery],
queryFn: async () => {
const res = await fetch(`/api/search?q=${debouncedQuery}`)
return res.json()
},
enabled: debouncedQuery.length > 0
})
}
Такой подход обеспечивает стабильность queryKey и предотвращает мгновенные повторные запросы при каждом изменении ввода.
TanStack Query работает наиболее эффективно, когда управление частотой запросов вынесено из queryFn и queryKey. Дебаунсинг в queryFn считается антипаттерном, поскольку приводит к неконтролируемым побочным эффектам и усложняет кэширование.
Правильная модель предполагает:
Флаг enabled часто используется совместно с дебаунсом для предотвращения запросов при промежуточных состояниях:
useQuery({
queryKey: ['users', debouncedSearch],
queryFn: fetchUsers,
enabled: Boolean(debouncedSearch)
})
Дополнительная логика может включать фильтрацию минимальной длины строки или проверку валидности параметров.
В отличие от дебаунса, троттлинг гарантирует выполнение запроса не чаще определённого интервала времени. Это важно в сценариях, где необходимо получать промежуточные результаты, но с ограничением частоты.
Типичный пример — скроллинг, где запросы могут происходить при достижении определённых порогов.
Реализация троттлинга через lodash:
import { useState, useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import throttle from 'lodash.throttle'
function useThrottledValue(value, delay) {
const [throttled, setThrottled] = useState(value)
const throttledSetter = useMemo(
() => throttle(setThrottled, delay),
[delay]
)
useEffect(() => {
throttledSetter(value)
}, [value, throttledSetter])
return throttled
}
Далее значение используется в queryKey аналогично дебаунсу.
При работе с бесконечными списками (useInfiniteQuery) дебаунсинг часто применяется к параметрам фильтрации, но не к самой пагинации. Пагинация должна оставаться детерминированной, иначе кэш страниц теряет консистентность.
useInfiniteQuery({
queryKey: ['products', debouncedFilter],
queryFn: ({ pageParam = 0 }) =>
fetch(`/api/products?filter=${debouncedFilter}&page=${pageParam}`),
getNextPageParam: (lastPage) => lastPage.nextCursor
})
В данном случае изменение фильтра приводит к полной смене ключа, а значит — сбросу кэша страниц.
Дебаунсинг часто путается с управлением актуальностью данных, но эти механизмы решают разные задачи. staleTime определяет, как долго данные считаются свежими и не требуют повторного запроса при повторном монтировании или фокусе окна.
useQuery({
queryKey: ['profile', userId],
queryFn: fetchProfile,
staleTime: 1000 * 60
})
При корректной настройке staleTime количество запросов может существенно уменьшиться, снижая необходимость агрессивного дебаунса.
TanStack Query уже включает встроенную дедупликацию: если несколько компонентов запрашивают одинаковый queryKey, выполняется один запрос. Это снижает потребность в ручном троттлинге в некоторых сценариях.
Однако при динамических ключах (например, ввод пользователя) дедупликация не заменяет дебаунс, поскольку ключи всё равно меняются слишком часто.
В сложных приложениях дебаунс и троттл часто реализуются через:
Пример с RxJS:
search$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(query => fetch(`/api?q=${query}`).then(r => r.json()))
)
В такой архитектуре TanStack Query может использоваться только для кэширования результата, а поток управления запросами остаётся вне его зоны ответственности.
Ключевой аспект корректного применения дебаунса заключается в стабильности queryKey. Любое нестабильное значение приводит к:
Оптимальная модель предполагает, что queryKey изменяется только после завершения логической паузы ввода или завершения операции изменения фильтров.
В некоторых интерфейсах требуется мгновенная реакция UI при ограничении сетевых вызовов. В таких случаях локальное состояние обновляется без задержки, а запросы синхронизируются через throttled значение.
Это создаёт разрыв между отображаемым состоянием и состоянием сервера, который компенсируется механизмами refetch и invalidationQueries.
const mutation = useMutation({
mutationFn: updateItem,
onSuccess: () => {
queryClient.invalidateQueries(['items'])
}
})
При троттлинге важно учитывать вероятность конфликтов между локальными изменениями и серверными данными.
Поисковые интерфейсы являются основным кейсом применения дебаунса в TanStack Query. Правильная архитектура обычно включает:
Такая комбинация позволяет добиться предсказуемого поведения без перегрузки API.
Наиболее распространённые проблемы:
Каждая из этих ошибок приводит к деградации кэширования и увеличению числа сетевых запросов.
Изменение задержки дебаунса во время работы компонента приводит к пересозданию таймеров и потенциальному сдвигу queryKey-обновлений. Это важно учитывать при динамической настройке UX-параметров, особенно в адаптивных интерфейсах.
Стабильность пользовательского опыта напрямую зависит от согласованности между временем задержки и частотой обновления queryKey.