Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яка різниця між useFetch та useAsyncData в Nuxt?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**useFetch** автоматично генерує ключ кешу з URL і параметрів, що робить його зручним для простих HTTP-запитів. **useAsyncData** потребує ручного ключа, але приймає будь-яку async-функцію. ```js const { data } = await useFetch('/api/users') const { data } = await useAsyncData('users', () => $fetch('/api/users')) ``` **Головне:** один endpoint - `useFetch`; кастомна логіка або кілька запитів - `useAsyncData`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**useFetch** - це composable для HTTP-запитів з автоматичним кешуванням за URL, а **useAsyncData** виконує будь-який async-код і кешує результат під ключем, який ти задаєш вручну. ## Теорія ### TL;DR - `useFetch` - як замовлення в ресторанному застосунку: даєш URL, він сам трекає запит і кешує результат. - `useAsyncData` - як готуєш сам: повний контроль над кожним кроком, ім'я рецепту задаєш ти. - Головна різниця: `useFetch` автоматично генерує ключ кешу з URL і параметрів; `useAsyncData` потребує рядкового ключа від тебе. - Один endpoint? `useFetch`. Фільтрація, join або кілька запитів? `useAsyncData`. - Швидке правило: якщо можна записати як один URL - бери `useFetch`. ### Швидкий приклад ```vue <!-- useFetch: ключ кешу = '/api/users' --> <script setup> const { data: users, pending } = await useFetch('/api/users') </script> <!-- useAsyncData: ручний ключ 'active-users', кастомна логіка --> <script setup> const { data } = await useAsyncData('active-users', () => $fetch('/api/users').then(users => users.filter(u => u.active)) ) </script> ``` `useFetch` кешує сиру відповідь під URL. `useAsyncData` кешує відфільтрований результат під `'active-users'`. Джерело те саме, але виходи різні, і ключі теж. ### Головна відмінність `useFetch` перетворює URL і параметри запиту на унікальний ключ кешу автоматично, тому реагує на зміни маршруту без додаткового налаштування. `useAsyncData` використовує будь-який рядок, який ти передаєш як ключ. Це дає простір для довільного async-коду, але відповідальність за унікальність ключа лежить на тобі. Якщо дві сторінки використовують один ключ із різними формами даних (data shapes), перемагає останній запит і ти отримуєш застарілий кеш. ### Коли використовувати - Прямий запит до одного endpoint: `useFetch('/api/posts')` - Динамічні дані маршруту, що змінюються з URL: `useFetch` (реагує автоматично) - Фільтрація або трансформація відповіді: `useAsyncData` (обгорни `$fetch` у свою логіку) - Кілька паралельних запитів: `useAsyncData` з `Promise.all` всередині обробника - Лише client-side дані на кшталт стану WebSocket: `useAsyncData` з `server: false` ### Таблиця порівняння | Характеристика | useFetch | useAsyncData | |---|---|---| | Ключ кешу | Авто (хеш URL + параметрів) | Ручний (обов'язковий рядок) | | Обробник | URL-рядок | Будь-яка async-функція | | Реактивність | Авто на зміну маршруту | Вручну через ключ | | Fetcher за замовчуванням | `$fetch` | Немає, передаєш свій | | Lazy-режим | Вбудована опція | Вбудована опція | | SSR payload | Додається автоматично | Додається якщо ключ унікальний | | Для чого | Прості API-запити | Фільтрація, join, складна логіка | ### Як працює кешування Обидва composable реєструють async-задачі під час SSR, призупиняють рендер до завершення обробника і серіалізують результат в HTML-пейлоад для гідратації (hydration). `useFetch` обчислює `hash(url + options)` і зберігає результат в `useNuxtApp().payload.data` під цим хешем. `useAsyncData` записує твій рядок у той самий сховок напряму. На клієнті, якщо ключ вже є в пейлоаді, повторного мережевого запиту не відбувається. ### Типові помилки **Відсутній ключ у useAsyncData** ```js // Неправильно - ключ обов'язковий в Nuxt 3 const { data } = await useAsyncData(async () => $fetch('/api/posts')) // Правильно const { data } = await useAsyncData('posts', async () => $fetch('/api/posts')) ``` **Статичний ключ для динамічних даних** ```js // Неправильно - id змінюється, але ключ 'posts' статичний // При переходах між записами завжди повертаються перші дані const { data } = await useAsyncData('posts', () => $fetch(`/api/posts/${id}`)) // Правильно - ключ змінюється разом з id const { data } = await useAsyncData(`posts-${id}`, () => $fetch(`/api/posts/${id}`)) ``` Це помилка, яку я найчастіше зустрічав на code review. Людина бачить, що дані не оновлюються при переходах між записами, додає `watch`, і все одно не розуміє чому не працює. А причина в ключі. **useFetch там, де потрібна трансформація відповіді** ```js // Працює, але data - це сира відповідь без фільтрації const { data } = await useFetch('/api/search', { body: { q: 'nuxt' } }) // Краще коли потрібен парсинг або фільтрація const { data } = await useAsyncData('search-nuxt', () => $fetch('/api/search', { method: 'POST', body: { q: 'nuxt' } }) .then(res => res.results.filter(r => r.published)) ) ``` ### Де зустрічається в реальних проектах - Nuxt Content: `useAsyncData('content', () => queryContent().find())` - Nuxt Auth (sidebase): `useFetch('/api/auth/me')` для даних сесії - Supabase + Nuxt: `useAsyncData(`posts-${userId}`, () => supabase.from('posts').select())` для row-level security - Інвалідація кешу після мутації: `refreshNuxtData('key')` для повторного завантаження, `clearNuxtData('key')` щоб видалити значення ### Питання на співбесіді **Q:** Що відбудеться, якщо два компоненти використовують `useAsyncData` з однаковим ключем, але різними обробниками? **A:** Другий виклик поверне закешований результат першого. Для спільних даних це може бути навмисним, але якщо обробники різні - ти отримаєш неправильний результат. Завжди використовуй унікальні ключі для кожної форми даних. **Q:** Чи підтримує `useFetch` POST-запити? **A:** Так. Передай `method: 'POST'` і `body` як параметри. Але якщо ще й потрібно трансформувати відповідь, краще переключитися на `useAsyncData` - так зрозуміліше про намір коду. **Q:** Що робить опція `server: false` в `useAsyncData`? **A:** Пропускає обробник під час SSR і виконує його тільки на клієнті. Використовуй для даних, що залежать від браузерних API або не повинні потрапляти в HTML-пейлоад, наприклад live-дані дашборду. **Q:** Як інвалідувати кеш `useAsyncData` після мутації у великому застосунку? **A:** Виклич `refreshNuxtData('key')` для повторного запиту або `clearNuxtData('key')` щоб повністю видалити значення з кешу. Поєднуй із `refresh()`, який повертає `useFetch`, для оптимістичних оновлень. **Q:** Як `useFetch` реагує на зміну реактивних query-параметрів? **A:** Передай реактивний об'єкт в опцію `query`. `useFetch` відстежує зміни і автоматично повторює запит - це одна з причин обирати його замість `$fetch` всередині `watchEffect`. ## Приклади ### Список продуктів за динамічною категорією ```vue <!-- pages/products/[category].vue --> <script setup> const route = useRoute() // Ключ кешу: '/api/products/electronics?limit=20&sort=price' // Автоматично оновлюється при зміні route.params.category const { data: products, pending } = await useFetch( `/api/products/${route.params.category}`, { query: { limit: 20, sort: 'price' } } ) </script> <template> <div v-if="pending">Завантаження...</div> <ul v-else> <li v-for="p in products" :key="p.id">{{ p.name }}</li> </ul> </template> ``` `useFetch` тут достатньо, бо URL вже містить всю варіацію. Ручний ключ і watcher не потрібні. ### Дашборд з паралельними запитами та обробкою часткових помилок ```vue <script setup> const { data: userId } = useAuth() // твій auth composable // Унікальний ключ для кожного користувача, паралельні запити, часткові помилки не ламають все const { data: dashboard, error } = await useAsyncData( `dashboard-${userId.value}`, async () => { const [stats, recent, notifications] = await Promise.allSettled([ $fetch('/api/stats'), $fetch('/api/recent-activity'), $fetch('/api/notifications') ]) // Статистика обов'язкова - кидаємо помилку якщо недоступна if (stats.status === 'rejected') throw new Error('Stats unavailable') return { stats: stats.value, recent: recent.status === 'fulfilled' ? recent.value : [], notifications: notifications.status === 'fulfilled' ? notifications.value : [] } }, { default: () => null } ) </script> ``` `useAsyncData` підходить тут, бо три окремі запити, крок злиття і кешування на рівні кожного користувача - це не те, для чого розроблявся `useFetch`. Ключ включає `userId`, тож кожен користувач отримує свій окремий пейлоад. Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.