useAsyncData и $fetch
useFetch удобен, когда странице нужен один адрес API. Если данные собираются из нескольких запросов или приходят не по HTTP, используют useAsyncData — он принимает любую асинхронную функцию. Внутри такой функции обычно вызывают $fetch — функцию Nuxt для HTTP-запросов.
$fetch
$fetch — обёртка над fetch из JavaScript. Она сама разбирает JSON, бросает ошибку при ответе с кодом 4xx или 5xx и на сервере обращается к своим /api/... напрямую, без сетевого запроса:
<!-- app/pages/index.vue -->
<script setup lang="ts">
async function showCount() {
const posts = await $fetch('/api/posts')
alert(`Статей в блоге: ${posts.length}`)
}
</script>
<template>
<section>
<button @click="showCount">Сколько статей?</button>
</section>
</template>После нажатия кнопки появится окно с числом статей. Тип ответа $fetch тоже знает: posts.length подсвечен без ошибок.
useAsyncData
useAsyncData делает с любой функцией то же, что useFetch с адресом: выполняет её на сервере и передаёт результат в браузер.
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { data: posts } = await useAsyncData('posts', () => $fetch('/api/posts'))
</script>
<template>
<section>
<h2>Главная</h2>
<p>Статей в блоге: {{ posts?.length }}</p>
</section>
</template>Первый аргумент — ключ, под которым Nuxt хранит данные и передаёт их в браузер. Второй — функция загрузки. На главной появится строка с числом статей. Запись useFetch('/api/posts') по сути сокращение для такого вызова.
Несколько запросов сразу
Странице статьи нужны сама статья и её комментарии. Загрузите их параллельно:
<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const id = route.params.id
const { data } = await useAsyncData(`post-${id}`, async () => {
const [post, comments] = await Promise.all([
$fetch(`/api/posts/${id}`),
$fetch(`/api/posts/${id}/comments`)
])
return { post, comments }
})
</script>
<template>
<article v-if="data">
<h2>{{ data.post.title }}</h2>
<p>{{ data.post.text }}</p>
<h3>Комментарии: {{ data.comments.length }}</h3>
<p v-for="(comment, i) in data.comments" :key="i">
<b>{{ comment.author }}:</b> {{ comment.text }}
</p>
</article>
</template>Promise.all запускает оба запроса одновременно. По адресу /posts/1 вы увидите статью и её комментарии.
Что выбрать
| Задача | Инструмент |
|---|---|
| Данные одного адреса API при открытии страницы | useFetch |
| Несколько запросов, база или своя логика при открытии страницы | useAsyncData с $fetch |
| Запрос по действию пользователя: кнопка, отправка формы | $fetch |
Частая ошибка
Вызвать $fetch прямо в <script setup>: const posts = await $fetch('/api/posts'). Страница покажется, но запрос выполнится дважды — на сервере при отрисовке и ещё раз в браузере при гидратации, ведь $fetch не передаёт результат в браузер. Для данных страницы оборачивайте $fetch в useAsyncData или берите useFetch.