Основи Запитів та Магія Ключів
Основи Запитів та Магія Ключів
Тепер, коли ми маємо налаштований інструмент, час навчитися ним користуватися. У цій главі ми розберемо useQuery — основний хук, який ви будете використовувати у 90% випадків.
Ми також заглибимося в Query Keys — концепцію, від якої залежить коректність роботи вашого кешу. Якщо ви зрозумієте ключі неправильно, ви отримаєте баги з кешуванням.
Анатомія useQuery
У версії 5 useQuery приймає тільки один аргумент — об'єкт з налаштуваннями. (Старий синтаксис useQuery(key, fn) більше не підтримується).
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const result = useQuery({
queryKey: ['user', userId], // 🔑 Унікальний ключ
queryFn: () => fetchUser(userId), // 📡 Функція запиту
});
// ...
}
Що повертає useQuery?
Об'єкт result містить все, що вам потрібно знати про стан запиту. Ось найважливіші поля:
| Поле | Тип | Опис |
|---|---|---|
data | TData | Дані, отримані з сервера. undefined, якщо запит ще не завершився. |
error | Error | Об'єкт помилки, якщо запит впав. |
isPending | boolean | v5: Запит йде, і даних в кеші немає. Це стан "першого завантаження". |
isLoading | boolean | Те саме, що isPending. (В v4 це було інакше, в v5 це аліас). |
isError | boolean | Чи сталася помилка. |
isFetching | boolean | Запит йде прямо зараз (в тому числі фоновий). |
isPending та isFetching:isPending: "У мене немає даних, покажи скелетон".isFetching: "Я оновлюю дані, покажи маленький спіннер збоку". Якщо у вас є старі дані (Stale),isPendingбудеfalse, аisFetching—true.
Типовий патерн використання
const { data, isPending, isError, error } = useQuery({ ... });
if (isPending) {
return <span>Loading...</span>;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
// Тут TypeScript вже знає, що data визначена
return <div>{data.title}</div>;
Query Keys: Ваші Залежності
queryKey — це масив, який ідентифікує запит. TanStack Query використовує його для:
- Кешування даних (це ключ в Map).
- Дедуплікації (якщо ключ однаковий, запит не дублюється).
- Автоматичного перезапуску (якщо ключ змінився, запит запускається знову).
queryKey як про масив залежностей useEffect. Все, що використовується всередині queryFn, має бути в ключі.Прості ключі
// Список всіх тудушок
['todos']
Ключі з параметрами
// Тудушка з ID 5
['todos', 5]
// Список тудушок, відфільтрований за статусом 'done'
['todos', { status: 'done' }]
Ієрархія ключів (Best Practice)
Ключі мають бути ієрархічними, від загального до конкретного. Це дозволить вам легко керувати групами запитів (наприклад, інвалідувати всі тудушки одразу).
Структура: ['resouce', 'related-id', 'params']
// ✅ Добре
['todos'] // Всі
['todos', 1] // Деталі ID 1
['todos', 'list', { page: 1 }] // Список з пагінацією
// ❌ Погано
['todos-1'] // Рядок важко парсити
[1, 'todos'] // Нелогічний порядок
Query Key Factories
Щоб уникнути хаосу і друкарських помилок ("users" vs "user"), професіонали використовують Key Factories.
Створіть файл queries/keys.ts:
export const todoKeys = {
all: ['todos'] as const,
lists: () => [...todoKeys.all, 'list'] as const,
list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
details: () => [...todoKeys.all, 'detail'] as const,
detail: (id: number) => [...todoKeys.details(), id] as const,
};
Використання:
useQuery({
queryKey: todoKeys.list('done'),
queryFn: ...
})
useQuery({
queryKey: todoKeys.detail(1),
queryFn: ...
})
Це дозволяє вам безпечно рефакторити ключі і мати автодоповнення.
Query Function (queryFn)
Функція, яку ви передаєте в queryFn, може бути будь-якою функцією, що повертає Promise.
Вимоги до queryFn:
- Повинна повертати Promise, який резолвиться з даними.
- Повинна кидати помилку (reject), якщо щось пішло не так.
fetch не кидає помилку на 4xx/5xx статуси (наприклад, 404). Він кидає помилку тільки при проблемах з мережею.
Ви мусите самі перевіряти response.ok.const fetchTodo = async (id: number) => {
const res = await fetch(`/api/todos/${id}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
return res.json();
};
Axios робить це автоматично, тому він часто зручніший.
Передача параметрів у queryFn
У v5 об'єкт context передається у queryFn автоматично. Він містить queryKey. Це зручно для написання універсальних функцій.
const fetchTodoByContext = async ({ queryKey }) => {
// queryKey[1] — це наш ID
const [_key, id] = queryKey;
const res = await fetch(`/api/todos/${id}`);
return res.json();
};
useQuery({
queryKey: ['todos', 5],
queryFn: fetchTodoByContext,
});
Але частіше використовують замикання (inline functions):
useQuery({
queryKey: ['todos', 5],
queryFn: () => fetchTodo(5), // Простіше і читабельніше
});
Встановлення та Налаштування: Фундамент
Правильний старт — половина успіху. У цій главі ми не просто встановимо бібліотеку, а налаштуємо її так, щоб вона працювала на нас, а не проти нас. Ми також підключимо DevTools — інструмент, без якого робота з TanStack Query нагадує ходіння в темряві.
Синхронізація Даних: Життєвий Цикл Запиту
Одна з найскладніших речей для розуміння в TanStack Query — це те, як і коли він вирішує оновити дані. "Чому воно знову робить запит?", "Чому дані зникли?" — типові питання.