Доки по разработке
This project is maintained by teniryte
queryKey — это идентификатор записи в кешe. Ключ определяет:
useQuery/useQueries;invalidateQueries.['todos', userId]). Это встроенный стандарт TanStack Query.'todos'), следующие — параметры.undefined внутри ключа делает запрос уникальным: ['user', undefined] ≠ ['user'].as const, чтобы TypeScript не «расплющивал» кортеж.useMemo / useStableValue), иначе при каждом рендере будет новый ключ и новый кеш.const filters = useMemo(
() => ({ status, orderBy }),
[status, orderBy],
);
const queryKey = ['todos', filters] as const;
const query = useQuery({ queryKey, queryFn: () => fetchTodos(filters) });
Лайфхак: если нужно временно отключить часть ключа, заведите «пустышку» (
null) вместоundefined, чтобы позже одинаковые ключи продолжали совпадать.
const todoKeys = {
all: ['todos'] as const,
detail: (id: string) => ['todos', 'detail', id] as const,
list: (filters: TodoFilters) => ['todos', 'list', filters] as const,
} satisfies Record<string, (...args: any[]) => readonly QueryKey>;
Такой объект:
useQuery, invalidateQueries, ensureQueryData;string[]).const todoQueryOptions = (userId: string) => ({
queryKey: ['todos', { userId }],
queryFn: () => fetchTodos(userId),
});
queryClient.invalidateQueries({ queryKey: ['todos'] }) освежит все запросы, ключи которых начинаются с 'todos': например, ['todos'], ['todos', { status: 'done' }].
Используйте объект exact: true, если нужно затронуть только конкретный ключ.
await queryClient.invalidateQueries({
queryKey: ['todos', { userId }],
exact: true,
});
Дополнительные фильтры:
type: 'active' | 'inactive' | 'all' — учитывает состояние наблюдателей;predicate: query => query.queryKey[1]?.status === 'done' — тонкая выборка;refetchType: 'active' | 'all' — контролирует, какие запросы перезапрашивать.await queryClient.invalidateQueries({
queryKey: todoKeys.list({ status: 'done' }),
predicate: q => q.getObserversCount() > 0, // только видимые экраны
});
Совет: при массовой инвалидации на сервере используйте
prefetchQueryсразу послеinvalidateQueries, чтобы прогреть кеш до входа пользователя на страницу SSR/SSG.
С v5 рекомендуется описывать ключ и настройки в одной функции:
export const userQuery = (id: string) => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
staleTime: 10 * 60_000,
});
// reuse
const { data } = useQuery(userQuery(id));
await queryClient.prefetchQuery(userQuery(id));
await queryClient.ensureQueryData(userQuery(id)); // безопасно для SSR/потокового рендера
Это исключает рассинхронизацию при инвалидации, префетче и SSR.
['todos', 'list', filters]), чтобы легко отсеивать только списки или только детали.export const projectKeys = {
all: () => ['projects'] as const,
detail: (projectId: string) => [...projectKeys.all(), projectId] as const,
tasks: (projectId: string, filter: TaskFilter) =>
[...projectKeys.detail(projectId), 'tasks', filter] as const,
};
Лайфхак: когда нужно «разорвать» кэш-цепочку (например, после logout), используйте числовой версиионинг ['user', sessionVersion]. При смене версии вся иерархия ключей станет новой.
queryClient.getQueryData(['todos', { userId }]);
queryClient.setQueryData(['todos', { userId }], updater);
queryClient.removeQueries({ queryKey: ['drafts'] });
getQueriesData(['todos']) возвращает массив пар [queryKey, data] для всех соответствий — удобно для массовых апдейтов.
Другие полезные методы:
getQueryState(queryKey) — проверяет наличие данных без их извлечения.setQueriesData({ queryKey }, updater) — пакетное обновление по маске.ensureQueryData(options) — гарантирует наличие данных, под капотом делает fetch.const optimisticId = crypto.randomUUID();
queryClient.setQueryData(todoKeys.list(filters), old =>
old?.map(todo => (todo.id === optimisticId ? { ...todo, title } : todo)),
);
queryClient.invalidateQueries({ queryKey: todoKeys.list(filters) }); // мягкий sync
Тонкость:
setQueryDataне запускаетqueryFn, поэтому обязательно инвалидируйте или вручную синхронизируйте сервер, иначе рискуете остаться с «фантомными» данными.
queryKeyHashFnПо умолчанию ключ сериализуется в JSON-строку. Если вам нужно поддержать, например, Map, определите хэш-функцию:
const queryClient = new QueryClient({
queryKeyHashFn: key => superjson.stringify(key),
});
Используйте осторожно: разные функции хэша между вкладками разрушат персистентность.
Дополнительно:
queryKeyHashFn влияет на localStorage/sessionStorage персистеры и broadcastQueryClient.Date.now() внутри приведёт к бесконечному росту кеша.const queryKeys = { todos: ['todos'] as const }.['report', reportId ?? 'ghost'], чтобы зависимость была явной.queryKey с broadcastQueryClient({ queryClient }).query.hash в Devtools, чтобы понять, не расходятся ли ключи инициализации/инвалидации.// 1. Таблица с пагинацией + фильтрами
const tableQuery = (params: TableParams) => ({
queryKey: ['table', 'list', params] as const,
queryFn: () => fetchTable(params),
placeholderData: keepPreviousData, // плавная смена страниц
});
// 2. Поддержка offline-first
queryClient.setDefaultOptions({
queries: {
gcTime: 1000 * 60 * 60, // 1h
structuralSharing: true,
},
});
// 3. Мгновенный локальный поиск по уже загруженному кешу
const cachedTodos = queryClient.getQueriesData({
queryKey: todoKeys.list({}),
});