В TanStack Query ключ запроса (query key) является фундаментальной частью системы кэширования, дедупликации и управления состоянием серверных данных. Динамические query keys используются для того, чтобы один и тот же логический запрос мог адаптироваться к входным параметрам: фильтрам, идентификаторам сущностей, пагинации, сортировке и любым другим переменным, влияющим на результат.
Ключ запроса представляет собой сериализуемую структуру, чаще всего массив, который однозначно идентифицирует данные в кэше. Именно изменение ключа запускает новый запрос и приводит к созданию отдельной записи в кэше.
На практике query key почти всегда оформляется в виде массива:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Такой ключ статичен и подходит только для неизменяемых наборов данных. Любое расширение логики требует введения динамических элементов:
useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId)
})
Здесь userId превращает один логический запрос в
множество независимых кэш-записей.
Каждое изменение значения внутри query key формирует отдельную сущность кэша. Это означает, что:
['users', 1] и ['users', 2] — разные
записиЭто поведение делает query key основным инструментом сегментации данных.
Наиболее частые сценарии использования динамических ключей:
useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId)
})
Любое изменение postId приводит к новому запросу и
независимому кэшу.
useQuery({
queryKey: ['products', { category, priceRange, sort }],
queryFn: () => fetchProducts({ category, priceRange, sort })
})
Объекты внутри ключа допустимы, но требуют строгой стабильности структуры.
Важно, что TanStack Query сравнивает ключи глубоко, но не защищает от нестабильных ссылок, если объект создаётся заново на каждом рендере.
useQuery({
queryKey: ['orders', { page, limit }],
queryFn: () => fetchOrders({ page, limit })
})
Каждая страница становится отдельным кэшем, если не используется infinite query.
Ключевой аспект динамических query keys — их стабильность. Даже если данные логически одинаковые, разные ссылки объектов могут привести к различным ключам.
Нестабильный вариант:
queryKey: ['users', { page: 1 }]
при каждом рендере создаёт новый объект. Несмотря на глубокое сравнение, подобная практика усложняет предсказуемость и может влиять на инструменты девтулов и рефетчинг.
Стабильный вариант:
const key = ['users', page]
useQuery({
queryKey: key,
queryFn: () => fetchUsers(page)
})
Для масштабируемых приложений применяется фабрика ключей. Это позволяет унифицировать структуру кэша и избегать ошибок.
const userKeys = {
all: ['users'],
lists: () => [...userKeys.all, 'list'],
list: (filters) => [...userKeys.lists(), filters],
detail: (id) => [...userKeys.all, 'detail', id]
}
Использование:
useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId)
})
Преимущества подхода:
Инвалидация работает по частичному совпадению ключей:
queryClient.invalidateQueries({
queryKey: ['users']
})
Это затронет:
['users']['users', 1]['users', { filters }]Иерархическая структура ключей позволяет управлять группами данных.
queryKey: ['data', { filter: getFilter() }]
Если getFilter() возвращает новый объект каждый раз,
ключи становятся трудно предсказуемыми.
Нежелательно использовать:
queryKey: ['data', new Date()]
Даже если это допустимо технически, ключ теряет предсказуемость.
Плохая практика:
['users', user, settings, true, 42]
Такой ключ сложно поддерживать и инвалидация становится неконтролируемой.
TanStack Query использует query key как индекс кэша. Это означает:
['users', 1] !== ['users', '1']
Строгая типизация структуры ключа становится критически важной.
При сложных доменных моделях ключи формируются композиционно:
const commentKeys = {
all: ['comments'],
byPost: (postId) => [...commentKeys.all, 'post', postId],
byUser: (userId) => [...commentKeys.all, 'user', userId]
}
Такой подход позволяет строить масштабируемую систему кэширования, где каждый уровень домена имеет собственное пространство ключей.
Порядок элементов в массиве query key строго влияет на идентичность:
['users', 1, 'profile'] !== ['users', 'profile', 1]
Это свойство используется для строгой сегментации запросов, но требует дисциплины при проектировании структуры ключей.
В прикладных системах ключи часто отражают REST-структуру:
Пример:
const postKeys = {
all: ['posts'],
lists: () => [...postKeys.all, 'list'],
list: (filters) => [...postKeys.lists(), filters],
detail: (id) => [...postKeys.all, 'detail', id],
comments: (id) => [...postKeys.detail(id), 'comments']
}
Такая структура формирует дерево данных, которое напрямую отражает зависимость сущностей.
TanStack Query предотвращает повторные запросы, если ключ и параметры запроса совпадают. Динамические ключи позволяют:
Это делает query key центральным элементом оптимизации сетевого слоя приложения.