Ви вже вмієте робити базові речі. Тепер перейдемо до технік "чорного поясу". Ці патерни допоможуть вирішити складні архітектурні завдання та оптимізувати продуктивність.
Часта ситуація: вам треба отримати користувача, а потім отримати його проекти, використовуючи userId.
В useEffect це пекло вкладеності. В TanStack Query це елегантно робиться через опцію enabled.
// 1. Отримуємо користувача
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: () => fetchUser(email),
});
const userId = user?.id;
// 2. Отримуємо проекти ТІЛЬКИ коли є userId
const { data: projects } = useQuery({
queryKey: ['projects', userId],
queryFn: () => fetchProjects(userId),
// Запит не запуститься, поки enabled === false
enabled: !!userId,
});
Поки user завантажується, userId буде undefined, і другий запит стоятиме на паузі (status: pending, fetchStatus: idle). Як тільки user завантажиться, другий запит автоматично полетить.
select)Іноді бекенд повертає величезний об'єкт, а вам потрібен лише маленький шматочок або відфільтрований список. Ви можете зробити це в компоненті, але тоді це буде перераховуватися при кожному рендері.
Опція select дозволяє трансформувати дані всередині Query. Результат буде мемоізований.
const { data: userNames } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers, // Повертає User[]
// Трансформуємо User[] -> string[]
select: (users) => users.map(u => u.name),
});
select виконується тільки тоді, коли data змінилася. Це чудова оптимізація продуктивності.Якщо у вас вже є дані (наприклад, ви перейшли зі списку на детальну сторінку, і у вас вже є title та summary поста), ви можете показати їх миттєво.
initialData vs placeholderDatainitialData: Вважається "справжніми" даними. Вони записуються в кеш. Якщо staleTime ще не вийшов, запит навіть не полетить.getServerSideProps).placeholderData: "Фейкові" дані. Вони не записуються в кеш. Вони зникають, як тільки прийдуть справжні дані.// Ми в списку (List View) вже маємо короткий опис поста
const { data: post } = useQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
initialData: () => {
// Шукаємо пост в кеші списку 'posts'
return queryClient.getQueryData(['posts'])?.find(p => p.id === id);
},
// Якщо ми знайшли дані, вважаємо їх свіжими 30 секунд
initialDataUpdatedAt: () => queryClient.getQueryState(['posts'])?.dataUpdatedAt,
});
За замовчуванням TanStack Query скасовує запит, якщо компонент демонтується або ключ змінюється (щоб не обробляти застарілу відповідь).
Щоб це працювало, ваша queryFn повинна підтримувати AbortSignal.
const fetchTodos = async ({ signal }) => {
const response = await fetch('/todos', {
// Передаємо сигнал у fetch
signal,
});
return response.json();
};
Axios підтримує це так:
const fetchTodos = async ({ signal }) => {
const response = await axios.get('/todos', {
signal,
});
return response.data;
};
Тепер, якщо користувач швидко клікає по категоріях, браузер скасує попередні HTTP-запити, заощаджуючи трафік.
useQueries)useQuery не можна викликати в циклі (правила хуків). Що робити, якщо у нас є масив ID [1, 2, 3], і нам треба завантажити кожен окремо?
Використовуйте useQueries (множина).
const ids = [1, 2, 3];
const results = useQueries({
queries: ids.map(id => ({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
staleTime: Infinity,
})),
});
// results — це масив результатів запитів
const isLoading = results.some(result => result.isPending);
Це дозволяє запускати довільну кількість запитів паралельно, дотримуючись правил хуків.
Пагінація та Infinite Scroll
Списки даних — це хліб і масло веб-розробки. Але завантажувати 10,000 записів одразу — погана ідея. Нам потрібна пагінація або "нескінченна прокрутка".
Архітектура та Best Practices
Ми вивчили API. Тепер поговоримо про те, як не перетворити проект на смітник. TanStack Query дає багато свободи, але "з великою силою приходить велика відповідальність".