Ты просишь Claude Code добавить на экран список товаров или таблицу лидеров, а он вместо простого fetch внутри useEffect тянет в проект новый пакет — @tanstack/react-query. Код разрастается, появляется незнакомое слово QueryClientProvider, и возникает вопрос: это ИИ усложняет то, что можно было сделать в три строки, или у него есть на то причина? Причина есть, и в большинстве React-проектов с сервером эта библиотека давно стала умолчанием, а не украшением. Разберёмся, что такое TanStack Query, за что она отвечает и когда её в проекте действительно ждать, а когда — нет.
Содержание
- Что такое TanStack Query простыми словами
- Почему ИИ сам добавляет её в проект
- Что конкретно делает библиотека
- Чем это отличается от простого fetch в useEffect
- Как устроен кеш изнутри
- Отправка данных на сервер: useMutation
- Связь с Supabase
- Когда это нужно учебному проекту, а когда избыточно
- Как проверить, что ИИ добавил её правильно
- Частые ошибки при использовании
- Частые вопросы
- Заключение
Что такое TanStack Query простыми словами
TanStack Query, до 2021 года известная как React Query, — библиотека для React (есть версии и под другие фреймворки, но нас интересует именно React-часть), которая берёт на себя работу с данными, приходящими с сервера: запрос, кеширование, обновление и обработку ошибок. Раньше эту роль в проекте играла связка useState и useEffect, которую разработчик писал вручную для каждого места, где нужны данные с бэкенда.
Разница похожа на разницу между тем, чтобы каждый раз готовить обед с нуля, и тем, чтобы держать на кухне холодильник. Без холодильника ты покупаешь продукты перед каждым приёмом пищи заново, даже если вчера купил то же самое. С холодильником — открыл дверцу, взял то, что уже есть, а свежее докупаешь только когда старое протухло. TanStack Query и есть такой холодильник для данных: один раз загруженный результат хранится в памяти приложения, и следующий компонент, которому нужны те же данные, берёт их оттуда, а не гоняет запрос заново.
Библиотека не заменяет сам HTTP-запрос — ты по-прежнему обращаешься к API так же, как описано в статье что такое API. TanStack Query оборачивает этот запрос в хук и добавляет вокруг него слой логики: кеш, повторные попытки, отслеживание состояния загрузки. Разработчики библиотеки сами описывают её как «недостающий слой для данных», которого не хватает в самом React: фреймворк умеет рисовать интерфейс, но ничего не знает о том, как эти данные получать, хранить и обновлять со временем.
Масштаб задачи, которую она решает, в небольшом проекте с одним запросом почти незаметен. Но стоит добавить второй экран, третий, общую таблицу рекордов, профиль пользователя — и без единого места, которое следит за состоянием всех этих данных, начинается хаос: где-то данные устарели, где-то дублируется один и тот же запрос, где-то забыли показать индикатор загрузки. TanStack Query берёт эту координацию на себя.
Главная мысль: TanStack Query — не замена fetch, а надстройка над ним. Она решает не «как отправить запрос», а «что делать с результатом»: где его держать, когда обновлять и что показывать, пока он ещё не пришёл.
Почему ИИ сам добавляет её в проект
Claude Code и похожие ассистенты обучены на огромном количестве реального React-кода, и в этом коде TanStack Query встречается постоянно — она стала фактическим стандартом для работы с сервером в экосистеме, обгоняя по распространённости самописные решения. Когда ИИ видит задачу «загрузить список с бэкенда», он предлагает инструмент, которым эту задачу чаще всего решают опытные разработчики, а не изобретает решение заново под каждый конкретный проект.
Есть и практическая причина. Ручная связка useState и useEffect для запроса выглядит примерно так: одна переменная состояния под сами данные, вторая под флаг загрузки, третья под текст ошибки, эффект с запросом внутри, try/catch вокруг него и не забыть про отмену запроса при размонтировании компонента. Это пять-шесть строк служебного кода на каждое место, где нужны данные, и в каждом из них легко упустить какую-то мелочь — например, забыть сбросить старую ошибку перед новым запросом или не остановить обновление состояния уже закрытого компонента. TanStack Query сводит это к одному вызову хука useQuery, внутри которого уже реализованы и обработка ошибок, и повторные попытки, и защита от лишних запросов и утечек памяти.
Меньше кода — меньше мест, где может закрасться ошибка, а значит и код, который выдаёт ИИ, получается надёжнее и предсказуемее. Когда ассистент пишет пять похожих экранов подряд, каждый со своим запросом к серверу, единый подход через useQuery избавляет от риска, что в одном месте обработка ошибки скопирована правильно, а в другом — с опечаткой.
Ещё один довод в пользу библиотеки — она проверена сообществом. Известные, широко используемые пакеты реже содержат скрытые ошибки, чем код, написанный с нуля под конкретную задачу, потому что тысячи разработчиков уже прогнали их через свои проекты и сообщили о проблемах, а авторы библиотеки эти проблемы исправили. Для новичка, который не может на глаз оценить качество чужого кода, это весомый плюс: ИИ выбирает инструмент, который уже проверен временем, а не рискует придумывать своё решение прямо в твоём проекте, где эту ошибку некому будет заметить.
Наконец, есть довод удобства сопровождения. Если через месяц ты попросишь ИИ добавить на экран новый блок с похожими данными, ему проще опереться на уже существующий в проекте useQuery, чем разбирать пять разных вариантов ручной загрузки, написанных в разное время и слегка отличающихся друг от друга.
Что конкретно делает библиотека
За фасадом одного хука useQuery скрывается несколько механизмов, которые обычно приходится реализовывать руками, если пишешь всё без библиотеки.
Кеш запросов. Каждый результат запроса сохраняется в памяти приложения под своим ключом — коротким идентификатором вроде «список рекордов» или «профиль пользователя». Если два разных компонента на странице запрашивают одни и те же данные, второй запрос не улетит на сервер, а получит готовый ответ из кеша. Это экономит и время загрузки, и нагрузку на сервер, особенно если данные показываются сразу в нескольких местах интерфейса — например, счёт игрока и в шапке, и на экране паузы.
Автоматические повторные попытки. Если запрос завершился ошибкой — например, из-за короткого сбоя сети или временной перегрузки сервера, — библиотека сама повторяет его через паузу, а не сразу показывает пользователю красный экран с ошибкой. Число попыток и задержку между ними можно настроить, но по умолчанию уже заложено разумное поведение: несколько попыток с постепенно растущей паузой между ними.
Stale-while-revalidate. Английский термин, который переводится примерно как «показывай устаревшее, пока проверяешь свежее». Когда пользователь возвращается на экран, где данные уже загружались раньше, TanStack Query сразу показывает то, что осталось в кеше, — интерфейс не мигает пустым состоянием и не показывает крутящийся индикатор загрузки заново. Параллельно в фоне уходит новый запрос, и когда он завершается, экран тихо обновляется актуальными данными. Пользователь почти никогда не видит паузу, хотя данные всё равно свежие.
Обновление при возврате на вкладку. Если пользователь свернул вкладку браузера, а потом вернулся к ней, библиотека по умолчанию перепроверяет данные — вдруг за это время что-то изменилось на сервере. Для таблицы лидеров или ленты заказов это особенно ценно: не нужно вручную нажимать «обновить», актуальные цифры подтягиваются сами, как только пользователь снова смотрит на экран.
Отслеживание состояния запроса. Хук useQuery возвращает не только сами данные, но и готовые флаги: идёт ли загрузка прямо сейчас, произошла ли ошибка и какая именно, есть ли уже хоть какие-то данные для показа, выполняется ли в этот момент фоновое обновление уже показанных данных. Компоненту не нужно эти флаги придумывать и обновлять вручную — они пересчитываются автоматически при каждом изменении состояния запроса, и в разметке остаётся только проверить нужный флаг и показать соответствующий блок интерфейса.
Отмена устаревших запросов. Если пользователь быстро переключается между вкладками фильтра, а старый запрос ещё не успел завершиться, библиотека умеет игнорировать его результат в пользу более свежего. Без этого механизма можно случайно получить ситуацию, когда медленный старый ответ приходит позже быстрого нового и перезаписывает актуальные данные устаревшими — на первый взгляд незаметная, но неприятная ошибка, которую сложно поймать вручную.
Чем это отличается от простого fetch в useEffect
Базовый способ получить данные с сервера в React уже знаком по курсу: вызов fetch внутри useEffect, а результат кладётся в состояние компонента через useState. Для одного экрана с одним запросом такой подход работает и ничего страшного в нём нет. Проблемы начинаются, когда таких мест в проекте становится несколько.
Каждое место с ручным fetch — это отдельная реализация одной и той же логики: свой флаг загрузки, своя обработка ошибки, свой try/catch, и если завтра нужно добавить повторную попытку при сбое — придётся переписывать её в каждом из этих мест по отдельности, а не поменять настройку в одном общем месте. Кеша между ними тоже нет: если два компонента на странице запрашивают одни и те же данные, оба честно сходят на сервер, даже если ответ придёт идентичный.
Есть и менее очевидная проблема — гонка запросов. Если пользователь быстро что-то переключил (например, фильтр в списке), а компонент при этом отправил новый fetch поверх ещё не завершившегося старого, оба запроса летят к серверу одновременно, и совершенно не гарантировано, что ответы придут в том порядке, в котором были отправлены. Без специальной защиты в коде экран может на мгновение показать устаревшие данные поверх свежих. Написать такую защиту руками несложно, но нужно про неё вспомнить и сделать это в каждом месте с запросом отдельно, а именно такие мелочи чаще всего забываются под давлением сроков.
TanStack Query решает всё это одним хуком, который используется везде одинаково, а результат хранится в общем кеше на всё приложение. Разница особенно заметна при переходах между экранами: без библиотеки при каждом возврате на экран данные загружаются заново и пользователь видит паузу, с библиотекой — данные уже лежат в кеше и появляются мгновенно, а свежая версия догружается в фоне.
Таблица ниже сводит разницу коротко.
| Fetch в useEffect | TanStack Query | |
|---|---|---|
| Загрузка/ошибка/повтор | Пишешь вручную под каждый запрос | Уже встроено в useQuery |
| Кеш между компонентами | Нет, каждый запрос отдельный | Общий кеш на всё приложение |
| Обновление при возврате на вкладку | Нужно писать самому | Работает по умолчанию |
| Повторный запрос при сбое сети | Нужно писать самому | Настроено из коробки |
| Защита от гонки запросов | Нужно писать самому | Встроена |
| Объём кода на один запрос | Пять-шесть строк служебной логики | Один вызов хука |
Стоит понимать: TanStack Query не отменяет знание того, как работает fetch и обычный useEffect — она строится поверх этих же основ и решает ту же задачу, просто без ручного повторения одной и той же обвязки в каждом компоненте. Для одного простого экрана с единственным запросом разница в опыте пользователя будет минимальной, и это нормально — библиотека раскрывает пользу именно там, где данных и мест их использования становится больше одного.
Как устроен кеш изнутри
Чтобы результат запроса попал в правильную ячейку общего кеша, у каждого запроса есть ключ — queryKey. Это массив значений, который однозначно описывает, какие именно данные хук хочет получить: например, ключ может состоять из строки «рекорды» и, если список фильтруется, ещё и выбранного периода. Пока ключ одинаковый, разные компоненты получают один и тот же результат из кеша, а если ключ отличается хотя бы одним значением — библиотека считает это другим запросом и хранит результат отдельно.
Именно ключи — самое частое место, где в проекте на TanStack Query возникает путаница. Если для списка всех рекордов и для списка рекордов конкретного игрока по ошибке использовать одинаковый ключ, экран покажет не те данные, которые ожидались, — библиотека честно отдаст то, что лежит в кеше под этим ключом, не заботясь о том, что смысл запроса на самом деле разный.
У кеша есть два важных параметра, которые определяют, как долго данные считаются актуальными. staleTime — время, в течение которого данные считаются свежими и повторный запрос при возврате на экран не отправляется вообще. gcTime (раньше называлось cacheTime) — время, в течение которого неиспользуемые данные ещё хранятся в памяти на случай, если компонент снова понадобится, прежде чем их удалят и освободят память. По умолчанию библиотека выставляет разумные значения для обоих параметров, и для учебного проекта их обычно не нужно трогать вручную — они уже настроены так, чтобы баланс между свежестью данных и числом лишних запросов был приемлемым для большинства сценариев.
Когда данные на сервере меняются — например, игрок поставил новый рекорд, — кеш нужно каким-то образом сообщить об этом. Библиотека предоставляет для этого механизм инвалидации: можно явно пометить конкретный ключ как устаревший, и при следующем обращении к нему запрос уйдёт на сервер заново, а не возьмёт старое значение из кеша. Обычно это делают сразу после того, как пользователь выполнил действие, которое меняет данные на сервере, — например, отправил новый счёт в таблицу рекордов.
Отправка данных на сервер: useMutation
Разбирая только загрузку данных, легко упустить вторую половину картины — что происходит, когда данные, наоборот, нужно отправить на сервер: сохранить новый рекорд, обновить профиль, удалить запись. Для этого в TanStack Query есть отдельный хук, useMutation, устроенный похожим образом на useQuery, но заточенный под изменяющие операции, а не под чтение.
Ключевая практическая связка между двумя хуками — как раз инвалидация кеша, о которой шла речь выше. После того как useMutation успешно отправил новый рекорд на сервер, естественный следующий шаг — сообщить useQuery, отвечающему за список рекордов, что его данные устарели и их пора перезапросить. Так таблица лидеров на экране обновляется автоматически сразу после того, как игрок поставил новый рекорд, без ручной перезагрузки страницы и без отдельного кода, который заново дёргает список.
Для учебного проекта с общей таблицей рекордов эта связка — типичный пример того, зачем вообще нужны оба хука вместе: useQuery показывает текущее состояние, useMutation его меняет, а инвалидация кеша соединяет их так, чтобы интерфейс не расходился с реальными данными на сервере.
flowchart TB
A["Компонент вызывает useQuery"] --> B["TanStack Query проверяет кеш"]
B -->|"данные свежие"| E["Показывает данные на экране"]
B -->|"данных нет или устарели"| C["Запрос к API"]
C --> D["Результат сохраняется в кеше"]
D --> E
Связь с Supabase
В проекте курса, где для таблицы рекордов «Змейки» используется Supabase (об этом был отдельный разбор), TanStack Query встаёт не вместо Supabase, а поверх него. Supabase остаётся тем местом, откуда физически приходят данные — база с рекордами и запросы к ней никуда не деваются. TanStack Query берёт на себя то, что происходит с этими данными уже на стороне приложения: кеширует ответ Supabase, чтобы при переходе с экрана игры на экран таблицы лидеров и обратно не гонять запрос заново, и следит, не устарели ли данные, пока пользователь их не видел.
Практическая выгода видна на простом сценарии. Игрок открывает таблицу лидеров, закрывает её и возвращается в игру, потом снова открывает таблицу. Без TanStack Query каждый такой переход — новый запрос к Supabase и короткая пауза с индикатором загрузки. С библиотекой второе открытие показывает уже закешированный список мгновенно, а в фоне тихо подгружается свежая версия — на случай, если за это время кто-то поставил новый рекорд.
Есть и более тонкий момент, который часто упускают из виду на старте: без кеша каждый лишний повторный запрос к Supabase — это реальный сетевой вызов, который расходует ресурсы проекта на стороне сервиса. Чем чаще пользователи переключаются между экранами с одними и теми же данными, тем заметнее становится разница между «запрашивать заново каждый раз» и «взять из кеша и обновить в фоне только когда действительно нужно». Для небольшого учебного проекта это не критично, но привычка работать правильно с самого начала избавляет от переделок, когда проект вырастет.
Стоит уточнить: TanStack Query не заменяет собой встроенные в Supabase механизмы вроде подписки на изменения в реальном времени (realtime). Если нужно, чтобы таблица лидеров обновлялась мгновенно у всех игроков одновременно, без ожидания следующего фокуса на вкладке, — это отдельная возможность самого Supabase, которая при желании тоже может сочетаться с TanStack Query, но это уже выходит за рамки базового использования библиотеки.
Когда это нужно учебному проекту, а когда избыточно
Для локального рекорда, который хранится в localStorage прямо на устройстве игрока, TanStack Query не нужна вовсе — там нет сетевого запроса, кешировать нечего, и подключать библиотеку ради одной строчки чтения из браузерного хранилища бессмысленно и только усложнит код без всякой пользы.
Ситуация меняется, когда в проекте появляется общая таблица лидеров с сервера или несколько экранов, которым нужны одни и те же данные. Это ровно тот момент, когда рекорд хранится не только у игрока на устройстве, а в общей базе — и его видят все, кто заходит в игру. Тут TanStack Query экономит заметный объём кода: вместо того чтобы на экране игры, на экране таблицы лидеров и в шапке с текущим счётом отдельно писать запрос к Supabase, достаточно одного вызова useQuery с одинаковым ключом — и все три места получат один и тот же закешированный результат.
Есть и промежуточный случай: один экран, но с данными, которые могут поменяться, пока пользователь на нём находится, — например, живой список онлайн-игроков. Здесь польза от библиотеки не в кеше между компонентами (компонент один), а в готовом механизме периодического обновления и обработки ошибок сети, который не пришлось бы писать вручную.
Общее правило простое: один экран с одним запросом, который выполняется один раз, — необязательно тащить библиотеку ради него. Несколько экранов, общие данные, необходимость их обновлять по ходу работы приложения — это тот случай, где TanStack Query окупается уже на втором-третьем месте использования. Если сомневаешься, стоит ли она в конкретном проекте, — спроси у ИИ прямо: «нужна ли здесь TanStack Query, или проще обойтись обычным fetch», и попроси коротко объяснить, почему он выбрал тот или иной вариант.
Как проверить, что ИИ добавил её правильно
Когда Claude Code говорит, что подключил TanStack Query, это легко перепроверить самому — без глубокого знания React.
Первое — открой package.json в корне проекта (об этом файле подробнее в статье что такое Node.js) и поищи в списке зависимостей строку @tanstack/react-query. Если её там нет, а ИИ утверждает, что библиотека подключена, — что-то не так, и стоит попросить показать, куда именно она была добавлена.
Второе — в корневом файле приложения (там, где рендерится всё дерево компонентов) должен появиться компонент QueryClientProvider, который оборачивает остальное приложение. Он нужен как раз потому, что именно там живёт тот самый «общий холодильник» — единый кеш, доступный всем компонентам ниже по дереву. Без этого провайдера хуки useQuery работать не будут и приложение выдаст ошибку прямо при запуске.
Третье — в компонентах, где раньше стоял ручной useEffect с fetch, должны появиться вызовы useQuery. Если код по-прежнему выглядит как отдельный useState под загрузку, отдельный под ошибку и fetch внутри useEffect — библиотеку либо не подключили по-настоящему, либо подключили только частично, и часть экранов осталась на старом ручном подходе.
Четвёртое — если в проекте есть операции изменения данных, вроде сохранения нового рекорда, поищи вызовы useMutation рядом с формой или кнопкой отправки. Их отсутствие само по себе не ошибка — можно отправлять данные и без этого хука, — но если ты просил ИИ сделать так, чтобы таблица лидеров обновлялась сразу после нового рекорда, а useMutation с инвалидацией кеша нигде не встречается, стоит уточнить, как это работает на самом деле.
Простой способ проверить работу вживую: открой экран с данными, дождись загрузки, перейди на другой экран и вернись обратно. Если данные появились мгновенно, без паузы и мигания, — кеш работает так, как задуман. Ещё один практический тест: отключи на секунду интернет, попробуй открыть экран заново, снова включи связь. Если библиотека настроена правильно, при восстановлении сети данные подгрузятся сами, без перезагрузки страницы.
Отдельно стоит попросить ИИ показать, каким ключом (queryKey) помечен запрос. Ключ — это способ, которым библиотека отличает один набор данных от другого: запрос списка товаров и запрос профиля пользователя не должны перепутаться в общем кеше. Если ключи для разных данных совпадают — на экране может показаться чужой результат, и это стоит попросить исправить.
Частые ошибки при использовании
Даже с готовой библиотекой остаются места, где легко ошибиться, — полезно знать о них, чтобы вовремя заметить проблему и попросить ИИ её исправить.
Одинаковые ключи для разных данных. Самая частая ошибка — уже упомянутая путаница с queryKey. Если два разных по смыслу запроса используют один и тот же ключ, один из них может незаметно подменить данные другого в кеше.
Забытая инвалидация после изменения данных. Если после сохранения нового рекорда через useMutation не сообщить кешу списка рекордов, что данные устарели, таблица лидеров на экране может показывать старые значения до тех пор, пока пользователь не откроет её заново или не пройдёт достаточно времени.
Провайдер добавлен не в том месте. Если QueryClientProvider обёрнут вокруг только части приложения, а не вокруг всего дерева компонентов, хуки useQuery в компонентах за пределами этой обёртки работать не будут и выдадут ошибку.
Слишком короткий или слишком длинный staleTime. Если время свежести данных выставлено слишком коротким, библиотека будет обновлять данные чаще, чем нужно, создавая лишнюю нагрузку. Если слишком длинным — пользователь может долго видеть устаревшую информацию, даже когда на сервере она уже изменилась. Для большинства учебных сценариев значение по умолчанию подходит без изменений.
Смешение серверного состояния с обычным. TanStack Query предназначена именно для данных с сервера. Локальное состояние интерфейса — открыто ли модальное окно, какой пункт меню выбран — не стоит хранить через неё, для этого по-прежнему подходит обычный useState.
Частые вопросы
TanStack Query — это то же самое, что React Query?
Да, это одна и та же библиотека. React Query — старое название, под которым она была известна до 2021 года, когда команда разработчиков расширила проект на другие фреймворки (Vue, Svelte, Solid) и переименовала его в TanStack Query. В React-проектах пакет по-прежнему называется @tanstack/react-query.
Нужно ли самому разбираться в TanStack Query, если код пишет ИИ?
Разбираться в деталях реализации — необязательно, но полезно понимать, зачем она в проекте и как проверить, что подключена правильно (см. раздел выше). Так ты сможешь заметить, если ИИ подключил библиотеку наполовину или использует её не по назначению, и попросить исправить.
TanStack Query заменяет Supabase или другой бэкенд?
Нет. TanStack Query не хранит данные и не отвечает на запросы сама — она управляет тем, что происходит с данными уже на стороне приложения, после того как они получены от Supabase, собственного сервера или любого другого источника.
Замедляет ли TanStack Query загрузку страницы?
Сама библиотека — нет, она лёгкая и не делает лишних запросов сверх необходимого. Наоборот, за счёт кеша повторные переходы между экранами обычно становятся быстрее, потому что не каждый раз гоняют запрос заново к серверу.
Можно ли добавить TanStack Query в проект, где уже есть ручные fetch и useEffect?
Можно, и обычно это делают постепенно: подключают библиотеку и провайдер, а затем переводят компоненты на useQuery по одному, не трогая сразу всё приложение. Старый и новый подход какое-то время могут работать в проекте параллельно, пока переход не завершится.
Что если ИИ вообще не предложил TanStack Query, а просто написал fetch в useEffect?
Для одного простого запроса это нормально и не ошибка. Если же в проекте уже несколько экранов с общими данными, а библиотеки всё ещё нет, стоит прямо попросить ИИ добавить TanStack Query и объяснить, зачем: меньше повторяющегося кода и общий кеш вместо отдельного запроса на каждом экране.
Заключение
TanStack Query — не усложнение ради усложнения, а инструмент, который берёт на себя рутину вокруг серверных данных: кеш, повторные попытки, обновление при возврате на вкладку, синхронизацию после изменений. Для одного простого запроса можно обойтись и голым fetch, но как только в проекте появляется несколько экранов с общими данными — библиотека экономит заметный объём кода и избавляет от однотипных ошибок в ручной обработке загрузки и ошибок. Если Claude Code добавил её в твой проект сам — это осознанный выбор проверенного стандарта, а не прихоть, и проверить его несложно: пакет в package.json, QueryClientProvider в корне приложения и useQuery вместо ручного useEffect в компонентах. Разобраться с формами, которые часто стоят рядом с такими запросами на одном экране, поможет статья про React Hook Form и валидацию.
Читай дальше
Все статьиНе просто статьи — тебя доведут до результата
В практикуме за 2499 ₽ рядом живая команда практикующих разработчиков и маркетологов: ведём по шагам до твоего работающего приложения. Не «ролики и сам разбирайся» — помогаем на каждом затыке.
Перейти к практикуму