Сборник продвинутых практик и экспертных советов по эффективному использованию TanStack Query (React Query) для управления состоянием асинхронных данных в React-приложениях.
TanStack Query (ранее React Query) кардинально изменил подход к управлению состоянием асинхронных данных в React-приложениях. Он берет на себя кэширование, фоновые обновления, инвалидацию и многое другое. Однако чтобы раскрыть его полный потенциал и избежать распространенных подводных камней, требуется глубокое понимание его философии. Мы собрали советы от экспертов, которые помогут вывести вашу работу с этой библиотекой на профессиональный уровень.
- **Правильно используйте Query Keys.** Ключи запросов — это не просто строки-идентификаторы, это зависимые массивы. Структурируйте их как вложенные коллекции, например, `['todos', 'list', { filter: 'active' }]` или `['user', userId, 'profile']`. Это позволяет точечно инвалидировать или обновлять связанные данные с помощью методов `invalidateQueries` или `setQueryData`, используя префиксы (например, `['todos']`).
- **Разделяйте concerns с помощью Custom Hooks.** Никогда не используйте `useQuery` напрямую в компонентах UI. Создавайте кастомные хуки для каждого запроса, например, `useUserProfile(userId)`. Это централизует конфигурацию (retry, staleTime), упрощает тестирование и позволяет легко менять источник данных без правки десятков компонентов.
- **Настройте глобальные дефолты в QueryClient.** Вместо того чтобы повторять одни и те же параметры в каждом хуке, задайте разумные значения по умолчанию при создании `QueryClient`: `defaultOptions: { queries: { staleTime: 5 * 60 * 1000, retry: 1, refetchOnWindowFocus: false } }`. Например, увеличение `staleTime` снижает количество ненужных фоновых запросов.
- **Используйте `staleTime` и `cacheTime` осознанно.** `staleTime` (по умолчанию 0) определяет, как долго данные считаются свежими. Установите ее в зависимости от характера данных: 30 секунд для чата, 10 минут для пользовательского профиля, Infinity для редко меняющихся справочников. `cacheTime` (по умолчанию 5 минут) определяет, как долго неактивные данные хранятся в кэше перед удалением. Для данных, к которым часто возвращаются, можно увеличить это значение.
- **Префетчинг данных — ключ к перцептивной производительности.** Используйте `queryClient.prefetchQuery` для предзагрузки данных, которые, вероятно, понадобятся пользователю. Например, при наведении на ссылку можно начать загружать данные для целевой страницы. Для этого идеально сочетать с событием `onMouseEnter`.
- **Оптимистичные обновления — must-have для UX.** При выполнении мутаций (`useMutation`) обновляйте кэш запроса вручную с помощью `setQueryData` до получения ответа от сервера (`onMutate`). В случае ошибки (`onError`) откатывайте изменения, используя контекст, сохраненный в `onMutate`. Это делает интерфейс мгновенно отзывчивым.
- **Инвалидация vs. фоновое обновление.** `invalidateQueries` помечает данные как устаревшие (stale), что приводит к их фоновому обновлению при следующем обращении. Если вам нужно немедленно обновить данные, используйте `refetchQueries`. Для бесшовного обновления после мутации часто лучше использовать `invalidateQueries` в `onSuccess`, чтобы не блокировать UI.
- **Эффективная работа с пагинацией и бесконечным скроллом.** Для пагинации используйте `useQuery`, где ключ запроса включает номер страницы. Для бесконечного скролла — `useInfiniteQuery`. Ключевой совет: сохраняйте данные всех загруженных страниц в кэше, но используйте `getNextPageParam` и `getPreviousPageParam` для управления потоком данных. Это позволяет пользователю мгновенно возвращаться к предыдущим страницам.
- **Отключайте автоматические запросы, когда нужно.** Не забывайте про опции `enabled`. Запрос можно поставить в зависимость от наличия данных: `enabled: !!userId`. Это избавляет от ошибок и лишних сетевых запросов. Также `enabled` можно использовать для отложенной загрузки данных по какому-либо триггеру.
- **Обрабатывайте ошибки глобально.** Настройте глобальные обработчики на уровне `QueryClient` через `defaultOptions`: `queries: { onError: (error) => toast.error(error.message) }` и `mutations: { onError: ... }`. Это избавляет от необходимости писать `try/catch` в каждом хуке, но не отменяет необходимости локальной обработки, где это требуется по логике.
- **Используйте `initialData` и `placeholderData`.** `initialData` позволяет сразу проинициализировать кэш данными (например, из SSR или другого запроса), что исключает состояние загрузки. `placeholderData` показывает «фантомные» данные только на время загрузки, не записывая их в кэш — идеально для скелетонов (skeletons).
- **Интегрируйте с инструментами состояния для синхронных данных.** TanStack Query управляет *асинхронным* состоянием. Для синхронного состояния (темы UI, модальные окна) используйте Zustand, Jotai или Context. Четкое разделение ответственности упрощает архитектуру.
- **Мониторинг и DevTools.** Включите `react-query/devtools` в dev-среде. Это неоценимый инструмент для отладки состояния кэша, ключей запросов и мутаций. Для продакшена рассмотрите логирование ключевых событий (успешные запросы, ошибки) для анализа проблем пользователей.
- **Тестируйте изолированно.** При тестировании компонентов, использующих TanStack Query, оборачивайте их в `QueryClientProvider` с тестовым экземпляром `QueryClient`. Используйте `queryClient.setQueryData` для предзаполнения кэша нужными данными в тестах. Для тестирования хуков в изоляции используйте `renderHook` из Testing Library.
Следуя этим советам, вы превратите TanStack Query из простого фетчера данных в мощный, предсказуемый и эффективный механизм управления состоянием вашего приложения, который значительно улучшит как опыт разработчика, так и пользовательский опыт.
Комментарии (11)