Dev Highlights

Доки по разработке

This project is maintained by teniryte

3. Query Keys и кеш

queryKey определяет идентичность данных в кеше. Если ключи спроектированы плохо, дальше будут проблемы с инвалидацией, SSR, optimistic UI и Devtools.

Правила хорошего ключа

  1. Всегда используйте массив.
  2. Первый сегмент делайте “namespace”: ['todos'], ['projects'].
  3. Аргументы кладите следующими сегментами: ['todos', userId].
  4. Сложные параметры упаковывайте в plain-object: ['todos', { status, page }].
  5. Не кладите в ключ функции, классы и несериализуемые значения.

Примеры

['todos']
['todos', userId]
['todos', 'list', { status, page, sort }]
['todos', 'detail', todoId]

Удачная схема:

Она сразу даёт удобные маски для инвалидции.

Фабрики ключей

export const todoKeys = {
  all: ['todos'] as const,
  lists: () => ['todos', 'list'] as const,
  list: (filters: TodoFilters) => ['todos', 'list', filters] as const,
  details: () => ['todos', 'detail'] as const,
  detail: (id: string) => ['todos', 'detail', id] as const,
}

Плюсы:

queryOptions и infiniteQueryOptions

Официально рекомендуемый паттерн:

import { queryOptions } from '@tanstack/react-query'

export function todoListOptions(filters: TodoFilters) {
  return queryOptions({
    queryKey: todoKeys.list(filters),
    queryFn: () => fetchTodoList(filters),
    staleTime: 30_000,
  })
}

Такие опции можно переиспользовать везде:

useQuery(todoListOptions(filters))
queryClient.prefetchQuery(todoListOptions(filters))
queryClient.ensureQueryData(todoListOptions(filters))

Для бесконечных списков используйте infiniteQueryOptions.

Частичное совпадение и инвалидация

await queryClient.invalidateQueries({ queryKey: ['todos'] })

Это затронет всё семейство:

Если нужна ровно одна запись:

await queryClient.invalidateQueries({
  queryKey: todoKeys.detail(id),
  exact: true,
})

Работа с кешем

queryClient.getQueryData(todoKeys.detail(id))
queryClient.getQueryState(todoKeys.detail(id))
queryClient.setQueryData(todoKeys.detail(id), updater)
queryClient.removeQueries({ queryKey: todoKeys.lists() })

Полезные методы:

Иммутабельность обязательна

queryClient.setQueryData(todoKeys.detail(id), (old) =>
  old
    ? {
        ...old,
        title: newTitle,
      }
    : old,
)

Нельзя мутировать old на месте. Это приводит к трудноуловимым багам и ломает предсказуемость ререндеров.

Что хранить в ключе, а что нет

В ключе храните:

Не храните в ключе:

Типичные ошибки

Практический паттерн

export const projectKeys = {
  all: ['projects'] as const,
  list: (filters: ProjectFilters) => ['projects', 'list', filters] as const,
  detail: (id: string) => ['projects', 'detail', id] as const,
  members: (id: string) => ['projects', 'detail', id, 'members'] as const,
}

Такой словарь даёт: