Хук useQuery — центральный механизм библиотеки TanStack
Query для получения, кэширования и синхронизации серверных данных в
приложении. Его основная задача — избавить приложение от ручного
управления состояниями загрузки, ошибок, кэширования и повторных
запросов.
В классическом подходе компонент самостоятельно хранит:
Без специализированной библиотеки код быстро становится перегруженным.
Пример типичного подхода через useEffect:
import { useEffect, useState } from 'react'
function Users() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
async function fetchUsers() {
try {
setLoading(true)
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка загрузки')
}
const data = await response.json()
if (!cancelled) {
setUsers(data)
}
} catch (err) {
if (!cancelled) {
setError(err)
}
} finally {
if (!cancelled) {
setLoading(false)
}
}
}
fetchUsers()
return () => {
cancelled = true
}
}, [])
if (loading) {
return <div>Загрузка...</div>
}
if (error) {
return <div>{error.message}</div>
}
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
В простом примере код ещё выглядит приемлемо. Но при масштабировании появляются дополнительные задачи:
useQuery переносит всю инфраструктурную работу внутрь
библиотеки.
Тот же пример через useQuery:
import { useQuery } from '@tanstack/react-query'
function Users() {
const {
data,
isLoading,
error
} = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка загрузки')
}
return response.json()
}
})
if (isLoading) {
return <div>Загрузка...</div>
}
if (error) {
return <div>{error.message}</div>
}
return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
Количество инфраструктурного кода значительно уменьшается.
useQueryuseQuery отвечает за:
Фактически useQuery превращает серверное состояние в
централизованный реактивный источник данных.
Одно из ключевых отличий TanStack Query от обычного
useState заключается в разделении двух типов состояния.
Клиентское состояние принадлежит интерфейсу:
const [isModalOpen, setIsModalOpen] = useState(false)
Примеры:
Такое состояние полностью контролируется приложением.
Серверное состояние приходит извне:
const users = await fetch('/api/users')
Особенности серверного состояния:
Именно для серверного состояния создан useQuery.
fetchГлавная проблема стандартного подхода — отсутствие централизованного управления серверными данными.
Например:
function Sidebar() {
// запрос пользователей
}
function Header() {
// тот же запрос пользователей
}
function Profile() {
// снова тот же запрос
}
Без общего кэша:
useQuery решает это через систему query cache.
Каждый запрос в useQuery хранится в централизованном
кэше.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Ключ:
['users']
используется как идентификатор данных.
Если другой компонент вызовет:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
новый запрос на сервер не выполнится.
Будут использованы уже закэшированные данные.
useQueryХук предоставляет множество состояний.
const { isLoading } = useQuery(...)
Активно при первом запросе.
const { error } = useQuery(...)
Содержит объект ошибки.
const { isSuccess } = useQuery(...)
Показывает успешное завершение.
const { isFetching } = useQuery(...)
Очень важное отличие от isLoading.
isLoading:
isFetching:
Пример:
if (isFetching) {
console.log('Идёт обновление')
}
Это позволяет показывать фоновую синхронизацию без очистки интерфейса.
useEffectuseEffect —
механизм жизненного циклаuseEffect не предназначен специально для работы с
серверными данными.
Он просто запускает побочный эффект:
useEffect(() => {
fetchUsers()
}, [])
Все остальные задачи разработчик реализует вручную.
useQuery —
специализированный data layeruseQuery — полноценный слой управления серверным
состоянием.
Он автоматически:
Одно из важнейших отличий.
При обычном fetch:
fetch('/api/users')
fetch('/api/users')
fetch('/api/users')
выполнятся три HTTP-запроса.
При использовании useQuery:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
запрос выполняется один раз.
Остальные компоненты получают данные из cache.
Если несколько компонентов одновременно запрашивают одинаковые данные:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
TanStack Query выполнит только один HTTP-запрос.
Все компоненты получат общий Promise.
Это называется request deduplication.
По умолчанию useQuery автоматически повторяет неудачные
запросы.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 3
})
Это особенно важно при:
Одно из фундаментальных отличий библиотеки — фоновая синхронизация данных.
Например:
useQuery может автоматически обновить данные:
useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
refetchOnWindowFocus: true
})
Это создаёт ощущение «живого» интерфейса.
В TanStack Query данные делятся на:
Fresh:
Stale:
Пример:
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
staleTime: 1000 * 60
})
В течение минуты данные считаются свежими.
useQuery строится вокруг двух ключевых сущностей.
Уникальный идентификатор данных.
['users']
или:
['user', userId]
Функция получения данных.
async () => {
const response = await fetch('/api/users')
return response.json()
}
Такое разделение позволяет:
useQueryКогда данные изменяются:
queryClient.invalidateQueries({
queryKey: ['users']
})
все компоненты автоматически обновляются.
Это похоже на глобальное реактивное хранилище серверных данных.
Многие приложения раньше хранили серверные данные в Redux.
Пример:
store.users.data
store.users.loading
store.users.error
Проблема такого подхода:
useQuery специализируется именно на server state.
SWR и TanStack Query решают похожие задачи:
Но у TanStack Query:
SWR обычно проще и легче, но менее функционален.
Apollo Client ориентирован на GraphQL.
useQuery из TanStack Query:
Пример:
useQuery({
queryKey: ['products'],
queryFn: () => axios.get('/api/products')
})
или:
useQuery({
queryKey: ['graphql-users'],
queryFn: fetchGraphQL
})
Глобальные store вроде:
обычно используются для client state.
Но серверное состояние имеет другую природу:
useQuery предоставляет специализированную инфраструктуру
именно для таких задач.
Ключевая идея библиотеки:
«Сервер — источник истины».
Клиент не пытается постоянно копировать серверное состояние вручную.
Вместо этого:
Это фундаментальное отличие от старых подходов, где разработчик полностью контролировал загрузку и хранение данных самостоятельно.
useQuery
особенно полезенuseQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Большое количество таблиц и списков:
Где требуется периодическая синхронизация.
С множеством повторно используемых данных.
Где важны:
useQuery может быть избыточнымДля простых приложений:
fetch('/api/ping')
использование полноценного query layer может быть неоправданным.
Особенно если:
Но по мере роста приложения преимущества становятся всё заметнее.
Ручной fetch:
fetch()
useEffect()
useState()
— это низкоуровневый механизм.
useQuery:
useQuery()
— полноценная система управления серверным состоянием.
Она включает:
Именно поэтому TanStack Query считается не просто библиотекой для запросов, а полноценным серверным state manager для React-приложений.