В классической модели TanStack Query запрос выполняется сразу после
того, как хук useQuery монтируется. Такой подход удобен для
большинства сценариев, где данные нужны немедленно: списки, профили,
настройки интерфейса.
Ленивые запросы (lazy queries) представляют противоположную модель поведения: запрос не запускается автоматически, а ожидает явного триггера. Это позволяет полностью контролировать момент получения данных, откладывая сетевой вызов до тех пор, пока он действительно необходим.
Ключевая идея ленивого запроса заключается в следующем:
refetch или изменения
состояния enabledТакой подход особенно важен в сценариях, где:
Основной способ реализации ленивого запроса в TanStack Query —
параметр enabled.
import { useQuery } from '@tanstack/react-query'
function Example() {
const query = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
enabled: false
})
return null
}
При enabled: false происходит следующее:
queryFn не вызывается при монтированииundefined, пока не произойдёт ручной
запускВнутренне TanStack Query продолжает отслеживать queryKey и сохраняет query в кеше, но выполнение откладывается.
После отключения автоматического выполнения основной механизм запуска
— метод refetch.
function Example() {
const { data, refetch, isFetching } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
enabled: false
})
return (
<div>
<button onCl ick={() => refetch()}>Загрузить данные</button>
{isFetching && <span>Загрузка...</span>}
{data && <pre>{JSON.stringify(data)}</pre>}
</div>
)
}
Поведение refetch:
queryFnenabledisFetching,
status)Promise, что позволяет работать с
результатом асинхронноВажно учитывать, что refetch всегда работает поверх
существующего query instance, а не создаёт новый запрос.
Второй распространённый подход — динамическое управление
enabled.
function Example({ userId }) {
const enabled = Boolean(userId)
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled
})
return null
}
Этот механизм отличается от refetch тем, что:
enabled в
trueФактически это декларативный вариант ленивого запроса, где запуск зависит от условий.
В реальных приложениях часто используется гибридный подход:
function Example() {
const [shouldLoad, setShouldLoad] = useState(false)
const query = useQuery({
queryKey: ['data'],
queryFn: fetchData,
enabled: shouldLoad
})
return (
<div>
<button onCl ick={() => setShouldLoad(true)}>
Активировать загрузку
</button>
<button onCl ick={() => query.refetch()}>
Перезагрузить
</button>
</div>
)
}
Такое разделение позволяет:
TanStack Query сохраняет данные независимо от того, был ли запрос ленивым или нет. Однако поведение кэша влияет на восприятие ленивого запроса.
Если данные уже есть в кеше:
enabled новый запрос может не выполняться
сразуstaleTimeЕсли данных нет:
data будет undefinedПример:
const query = useQuery({
queryKey: ['settings'],
queryFn: fetchSettings,
enabled: false,
staleTime: 1000 * 60
})
Даже при последующем включении enabled, поведение будет
зависеть от свежести кеша.
Одно из ключевых применений ленивых запросов — каскадные (dependent) запросы.
const { data: user } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
const userId = user?.id
const { data: posts } = useQuery({
queryKey: ['posts', userId],
queryFn: () => fetchPosts(userId),
enabled: !!userId
})
Здесь второй запрос становится ленивым до момента появления
userId.
Такой подход решает проблему:
Состояния TanStack Query при ленивом запуске отличаются от обычных:
При enabled: false:
status остаётся pendingisLoading не становится trueisFetching не активируетсяПосле refetch:
isFetching = truestatus переходит в loadingsuccess или errorЭто важно учитывать при построении UI, так как привычная логика загрузки может не сработать без явного триггера.
Поведение retry не зависит от того, ленивый запрос или нет.
const query = useQuery({
queryKey: ['data'],
queryFn: fetchData,
enabled: false,
retry: 2
})
Если refetch был вызван и запрос упал:
Классический пример ленивого запроса — поисковая форма без автозапроса.
function Search() {
const [query, setQuery] = useState('')
const [term, setTerm] = useState('')
const result = useQuery({
queryKey: ['search', term],
queryFn: () => searchApi(term),
enabled: false
})
const handleSearch = () => {
setTerm(query)
result.refetch()
}
return (
<div>
<input value={query} onCha nge={(e) => setQuery(e.target.value)} />
<button onCl ick={handleSearch}>Поиск</button>
</div>
)
}
В этой модели:
queryKey фиксирует состояние поискаХотя enabled: false и ленивые запросы часто
воспринимаются как одно и то же, между ними есть концептуальная
разница:
enabled — декларативное условие выполненияrefetch — императивный запускПервый вариант встроен в реактивную модель данных, второй — управляемое действие.
С точки зрения архитектуры:
enabled подходит для зависимостейrefetch подходит для событий пользователяВ сложных приложениях ленивые запросы применяются в комбинациях:
Пример с модальным окном:
function UserModal({ userId, open }) {
const query = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: open && !!userId
})
return null
}
Здесь ленивость определяется состоянием интерфейса, а не действиями пользователя напрямую.
Использование ленивых запросов меняет структуру взаимодействия с данными:
При этом важно избегать чрезмерного использования
refetch как универсального механизма, так как это может
привести к дублированию логики и усложнению управления состоянием.