Busca de Dados
Nuxt fornece composables para lidar com a busca de dados dentro da sua aplicação.
Nuxt vem com dois composables e uma biblioteca embutida para realizar busca de dados em ambientes de navegador ou servidor: useFetch
, useAsyncData
e $fetch
.
Em resumo:
$fetch
é a maneira mais simples de fazer uma requisição de rede.useFetch
é um wrapper em torno de$fetch
que busca dados apenas uma vez na renderização universal.useAsyncData
é semelhante aouseFetch
, mas oferece um controle mais detalhado.
Tanto useFetch
quanto useAsyncData
compartilham um conjunto comum de opções e padrões que detalharemos nas últimas seções.
A necessidade de useFetch
e useAsyncData
Nuxt é um framework que pode executar código isomórfico (ou universal) em ambientes de servidor e cliente. Se a função $fetch
for usada para realizar a busca de dados na função de configuração de um componente Vue, isso pode causar a busca de dados duas vezes, uma vez no servidor (para renderizar o HTML) e novamente no cliente (quando o HTML é hidratado). Isso pode causar problemas de hidratação, aumentar o tempo de interatividade e causar comportamentos imprevisíveis.
Os composables useFetch
e useAsyncData
resolvem esse problema garantindo que, se uma chamada de API for feita no servidor, os dados sejam encaminhados para o cliente no payload.
O payload é um objeto JavaScript acessível através de useNuxtApp().payload
. Ele é usado no cliente para evitar buscar novamente os mesmos dados quando o código é executado no navegador durante a hidratação.
Use o Nuxt DevTools para inspecionar esses dados na aba Payload.
<script setup lang="ts">
const { data } = await useFetch('/api/data')
async function handleFormSubmit() {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// Meus dados do formulário
}
})
}
</script>
<template>
<div v-if="data == null">
Sem dados
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- tags de entrada do formulário -->
</form>
</div>
</template>
No exemplo acima, useFetch
garantiria que a requisição ocorresse no servidor e fosse devidamente encaminhada para o navegador. $fetch
não possui tal mecanismo e é uma melhor opção para usar quando a requisição é feita apenas a partir do navegador.
Suspense
Nuxt usa o componente <Suspense>
do Vue para evitar a navegação antes que todos os dados assíncronos estejam disponíveis para a visualização. Os composables de busca de dados podem ajudá-lo a aproveitar esse recurso e usar o que for mais adequado em cada chamada.
Você pode adicionar o <NuxtLoadingIndicator>
para adicionar uma barra de progresso entre as navegações de página.
$fetch
Nuxt inclui a biblioteca ofetch, e é importada automaticamente como o alias $fetch
globalmente em toda a sua aplicação.
async function addTodo() {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// Meus dados do todo
}
})
}
Cuidado que usar apenas $fetch
não fornecerá deduplicação de chamadas de rede e prevenção de navegação. :br
Recomenda-se usar $fetch
para interações do lado do cliente (baseadas em eventos) ou combinado com useAsyncData
ao buscar os dados iniciais do componente.
Passar Cabeçalhos do Cliente para a API
Ao chamar useFetch
no servidor, o Nuxt usará useRequestFetch
para proxyar cabeçalhos e cookies do cliente (com exceção de cabeçalhos que não devem ser encaminhados, como host
).
const { data } = await useFetch('/api/echo');
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
Alternativamente, o exemplo abaixo mostra como usar useRequestHeaders
para acessar e enviar cookies para a API a partir de uma requisição do lado do servidor (originada no cliente). Usando uma chamada isomórfica $fetch
, garantimos que o endpoint da API tenha acesso ao mesmo cabeçalho cookie
originalmente enviado pelo navegador do usuário. Isso é necessário apenas se você não estiver usando useFetch
.
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser() {
return await $fetch('/api/me', { headers })
}
Você também pode usar useRequestFetch
para proxyar cabeçalhos para a chamada automaticamente.
Tenha muito cuidado antes de proxyar cabeçalhos para uma API externa e inclua apenas os cabeçalhos que você precisa. Nem todos os cabeçalhos são seguros para serem passados e podem introduzir comportamentos indesejados. Aqui está uma lista de cabeçalhos comuns que NÃO devem ser proxyados:
host
,accept
content-length
,content-md5
,content-type
x-forwarded-host
,x-forwarded-port
,x-forwarded-proto
cf-connecting-ip
,cf-ray
useFetch
O composable useFetch
usa $fetch
nos bastidores para fazer chamadas de rede seguras para SSR na função de configuração.
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Visitas à página: {{ count }}</p>
</template>
Este composable é um wrapper em torno do composable useAsyncData
e da utilidade $fetch
.
useAsyncData
O composable useAsyncData
é responsável por encapsular a lógica assíncrona e retornar o resultado assim que for resolvido.
useFetch(url)
é quase equivalente a useAsyncData(url, () => event.$fetch(url))
. :br
É uma simplificação para a experiência do desenvolvedor para o caso de uso mais comum. (Você pode descobrir mais sobre event.fetch
em useRequestFetch
.)
Existem alguns casos em que usar o composable useFetch
não é apropriado, por exemplo, quando um CMS ou um terceiro fornece sua própria camada de consulta. Nesse caso, você pode usar useAsyncData
para encapsular suas chamadas e ainda manter os benefícios fornecidos pelo composable.
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// Isso também é possível:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
O primeiro argumento de useAsyncData
é uma chave única usada para armazenar em cache a resposta do segundo argumento, a função de consulta. Essa chave pode ser ignorada passando diretamente a função de consulta, a chave será gerada automaticamente.
:br :br
Como a chave gerada automaticamente leva em consideração apenas o arquivo e a linha onde useAsyncData
é invocado, é recomendável sempre criar sua própria chave para evitar comportamentos indesejados, como quando você está criando seu próprio composable personalizado encapsulando useAsyncData
.
:br :br
Definir uma chave pode ser útil para compartilhar os mesmos dados entre componentes usando useNuxtData
ou para atualizar dados específicos.
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})
O composable useAsyncData
é uma ótima maneira de encapsular e aguardar a conclusão de várias requisições $fetch
, e então processar os resultados.
const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons'),
$fetch('/cart/offers')
])
return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
useAsyncData
é para buscar e armazenar em cache dados, não para acionar efeitos colaterais como chamar ações do Pinia, pois isso pode causar comportamentos indesejados, como execuções repetidas com valores nulos. Se você precisar acionar efeitos colaterais, use a utilidade callOnce
para fazê-lo.
const offersStore = useOffersStore()
// você não pode fazer isso
await useAsyncData(() => offersStore.getOffer(route.params.slug))
Valores de Retorno
useFetch
e useAsyncData
têm os mesmos valores de retorno listados abaixo.
data
: o resultado da função assíncrona que é passada.refresh
/execute
: uma função que pode ser usada para atualizar os dados retornados pela funçãohandler
.clear
: uma função que pode ser usada para definirdata
comoundefined
(ou o valor deoptions.default()
se fornecido), definirerror
comonull
, definirstatus
comoidle
e marcar quaisquer requisições pendentes como canceladas.error
: um objeto de erro se a busca de dados falhar.status
: uma string indicando o status da requisição de dados ("idle"
,"pending"
,"success"
,"error"
).
data
, error
e status
são refs do Vue acessíveis com .value
em <script setup>
Por padrão, o Nuxt espera até que um refresh
seja concluído antes que possa ser executado novamente.
Se você não buscou dados no servidor (por exemplo, com server: false
), então os dados não serão buscados até que a hidratação seja concluída. Isso significa que mesmo se você aguardar useFetch
no lado do cliente, data
permanecerá nulo dentro de <script setup>
.
Opções
useAsyncData
e useFetch
retornam o mesmo tipo de objeto e aceitam um conjunto comum de opções como seu último argumento. Eles podem ajudá-lo a controlar o comportamento dos composables, como bloqueio de navegação, armazenamento em cache ou execução.
Lazy
Por padrão, os composables de busca de dados esperarão pela resolução de sua função assíncrona antes de navegar para uma nova página usando o Suspense do Vue. Esse recurso pode ser ignorado na navegação do lado do cliente com a opção lazy
. Nesse caso, você terá que lidar manualmente com o estado de carregamento usando o valor status
.
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
lazy: true
})
</script>
<template>
<!-- você precisará lidar com um estado de carregamento -->
<div v-if="status === 'pending'">
Carregando ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- faça algo -->
</div>
</div>
</template>
Você pode alternativamente usar useLazyFetch
e useLazyAsyncData
como métodos convenientes para realizar o mesmo.
const { status, data: posts } = useLazyFetch('/api/posts')
Veja também api > composables > use-lazy-fetch
Veja também api > composables > use-lazy-async-data
Busca apenas no cliente
Por padrão, os composables de busca de dados executarão sua função assíncrona em ambientes de cliente e servidor. Defina a opção server
como false
para executar a chamada apenas no lado do cliente. No carregamento inicial, os dados não serão buscados antes que a hidratação esteja completa, então você terá que lidar com um estado pendente, embora na navegação subsequente do lado do cliente os dados sejam aguardados antes de carregar a página.
Combinado com a opção lazy
, isso pode ser útil para dados que não são necessários na primeira renderização (por exemplo, dados não sensíveis a SEO).
/* Esta chamada é realizada antes da hidratação */
const articles = await useFetch('/api/article')
/* Esta chamada será realizada apenas no cliente */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
O composable useFetch
é destinado a ser invocado no método de configuração ou chamado diretamente no nível superior de uma função em hooks de ciclo de vida, caso contrário, você deve usar o método $fetch
.
Minimizar o tamanho do payload
A opção pick
ajuda a minimizar o tamanho do payload armazenado no seu documento HTML selecionando apenas os campos que você deseja retornados dos composables.
<script setup lang="ts">
/* escolha apenas os campos usados no seu template */
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description']
})
</script>
<template>
<h1>{{ mountain.title }}</h1>
<p>{{ mountain.description }}</p>
</template>
Se você precisar de mais controle ou mapear sobre vários objetos, pode usar a função transform
para alterar o resultado da consulta.
const { data: mountains } = await useFetch('/api/mountains', {
transform: (mountains) => {
return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
}
})
Tanto pick
quanto transform
não impedem que os dados indesejados sejam buscados inicialmente. Mas eles impedirão que dados indesejados sejam adicionados ao payload transferido do servidor para o cliente.
Armazenamento em cache e refetching
Chaves
useFetch
e useAsyncData
usam chaves para evitar buscar novamente os mesmos dados.
useFetch
usa a URL fornecida como uma chave. Alternativamente, um valorkey
pode ser fornecido no objetooptions
passado como último argumento.useAsyncData
usa seu primeiro argumento como uma chave se for uma string. Se o primeiro argumento for a função handler que realiza a consulta, então uma chave que é única para o nome do arquivo e número da linha da instância deuseAsyncData
será gerada para você.
Para obter os dados em cache por chave, você pode usar useNuxtData
Estado Compartilhado e Consistência de Opções
Quando vários componentes usam a mesma chave com useAsyncData
ou useFetch
, eles compartilharão os mesmos refs data
, error
e status
. Isso garante consistência entre os componentes, mas requer que algumas opções sejam consistentes.
As seguintes opções devem ser consistentes em todas as chamadas com a mesma chave:
- Função
handler
- Opção
deep
- Função
transform
- Array
pick
- Função
getCachedData
- Valor
default
// ❌ Isso acionará um aviso de desenvolvimento
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
As seguintes opções podem diferir com segurança sem acionar avisos:
server
lazy
immediate
dedupe
watch
// ✅ Isso é permitido
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })
Se você precisar de instâncias independentes, use chaves diferentes:
// Estas são instâncias completamente independentes
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))
Chaves Reativas
Você pode usar refs computados, refs simples ou funções getter como chaves, permitindo busca de dados dinâmica que atualiza automaticamente quando as dependências mudam:
// Usando uma propriedade computada como chave
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value)
)
// Quando userId muda, os dados serão automaticamente buscados novamente
// e os dados antigos serão limpos se nenhum outro componente os usar
userId.value = '456'
Refresh e execute
Se você quiser buscar ou atualizar dados manualmente, use a função execute
ou refresh
fornecida pelos composables.
<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>
<template>
<div>
<p>{{ data }}</p>
<button @click="() => refresh()">Atualizar dados</button>
</div>
</template>
A função execute
é um alias para refresh
que funciona exatamente da mesma forma, mas é mais semântica para casos em que a busca não é imediata.
Para buscar novamente globalmente ou invalidar dados em cache, veja clearNuxtData
e refreshNuxtData
.
Clear
Se você quiser limpar os dados fornecidos, por qualquer motivo, sem precisar saber a chave específica para passar para clearNuxtData
, você pode usar a função clear
fornecida pelos composables.
const { data, clear } = await useFetch('/api/users')
const route = useRoute()
watch(() => route.path, (path) => {
if (path === '/') clear()
})
Watch
Para executar novamente sua função de busca cada vez que outros valores reativos em sua aplicação mudarem, use a opção watch
. Você pode usá-la para um ou vários elementos observáveis.
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* Alterar o id acionará uma nova busca */
watch: [id]
})
Note que observar um valor reativo não mudará a URL buscada. Por exemplo, isso continuará buscando o mesmo ID inicial do usuário porque a URL é construída no momento em que a função é invocada.
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id]
})
Se você precisar mudar a URL com base em um valor reativo, pode querer usar uma URL computada em vez disso.
URL Computada
Às vezes, você pode precisar computar uma URL a partir de valores reativos e atualizar os dados cada vez que esses valores mudarem. Em vez de contornar isso, você pode anexar cada parâmetro como um valor reativo. O Nuxt usará automaticamente o valor reativo e buscará novamente cada vez que ele mudar.
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id
}
})
No caso de uma construção de URL mais complexa, você pode usar um callback como um getter computado que retorna a string da URL.
Toda vez que uma dependência mudar, os dados serão buscados usando a URL recém-construída. Combine isso com não-imediato, e você pode esperar até que o elemento reativo mude antes de buscar.
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
immediate: false
})
const pending = computed(() => status.value === 'pending');
</script>
<template>
<div>
{/* desabilitar a entrada enquanto busca */}
<input v-model="id" type="number" :disabled="pending"/>
<div v-if="status === 'idle'">
Digite um ID de usuário
</div>
<div v-else-if="pending">
Carregando ...
</div>
<div v-else>
{{ data }}
</div>
</div>
</template>
Se você precisar forçar uma atualização quando outros valores reativos mudarem, também pode observar outros valores.
Não imediato
O composable useFetch
começará a buscar dados no momento em que for invocado. Você pode impedir isso definindo immediate: false
, por exemplo, para esperar por uma interação do usuário.
Com isso, você precisará tanto do status
para lidar com o ciclo de vida da busca, quanto do execute
para iniciar a busca de dados.
<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('/api/comments', {
immediate: false
})
</script>
<template>
<div v-if="status === 'idle'">
<button @click="execute">Obter dados</button>
</div>
<div v-else-if="status === 'pending'">
Carregando comentários...
</div>
<div v-else>
{{ data }}
</div>
</template>
Para um controle mais refinado, a variável status
pode ser:
idle
quando a busca não começoupending
quando uma busca começou, mas ainda não foi concluídaerror
quando a busca falhasuccess
quando a busca é concluída com sucesso
Passando Cabeçalhos e Cookies
Quando chamamos $fetch
no navegador, cabeçalhos do usuário como cookie
serão enviados diretamente para a API.
Normalmente, durante a renderização do lado do servidor, por considerações de segurança, o $fetch
não incluiria os cookies do navegador do usuário, nem passaria cookies da resposta do fetch.
No entanto, ao chamar useFetch
com uma URL relativa no servidor, o Nuxt usará useRequestFetch
para proxyar cabeçalhos e cookies (com exceção de cabeçalhos que não devem ser encaminhados, como host
).
Passar Cookies de Chamadas de API do Lado do Servidor na Resposta SSR
Se você quiser passar/proxyar cookies na outra direção, de uma requisição interna de volta para o cliente, você precisará lidar com isso você mesmo.
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => {
/* Obter a resposta do endpoint do servidor */
const res = await $fetch.raw(url)
/* Obter os cookies da resposta */
const cookies = res.headers.getSetCookie()
/* Anexar cada cookie à nossa Requisição de Entrada */
for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie)
}
/* Retornar os dados da resposta */
return res._data
}
// Este composable passará automaticamente cookies para o cliente
const event = useRequestEvent()
const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
Suporte à API de Opções
Nuxt fornece uma maneira de realizar busca de asyncData
dentro da API de Opções. Você deve encapsular sua definição de componente dentro de defineNuxtComponent
para que isso funcione.
export default defineNuxtComponent({
/* Use a opção fetchKey para fornecer uma chave única */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello')
}
}
})
Usar <script setup>
ou <script setup lang="ts">
são as maneiras recomendadas de declarar componentes Vue no Nuxt.
Serializando Dados do Servidor para o Cliente
Ao usar useAsyncData
e useLazyAsyncData
para transferir dados buscados no servidor para o cliente (bem como qualquer outra coisa que utilize o payload do Nuxt), o payload é serializado com devalue
. Isso nos permite transferir não apenas JSON básico, mas também serializar e reviver/desserializar tipos de dados mais avançados, como expressões regulares, Datas, Map e Set, ref
, reactive
, shallowRef
, shallowReactive
e NuxtError
- e mais.
Também é possível definir seu próprio serializador/desserializador para tipos que não são suportados pelo Nuxt. Você pode ler mais na documentação de useNuxtApp
.
Note que isso não se aplica a dados passados das suas rotas de servidor quando buscados com $fetch
ou useFetch
- veja a próxima seção para mais informações.
Serializando Dados de Rotas de API
Ao buscar dados do diretório server
, a resposta é serializada usando JSON.stringify
. No entanto, como a serialização é limitada apenas a tipos primitivos do JavaScript, o Nuxt faz o seu melhor para converter o tipo de retorno de $fetch
e useFetch
para corresponder ao valor real.
Exemplo
export default defineEventHandler(() => {
return new Date()
})
// O tipo de `data` é inferido como string, embora tenhamos retornado um objeto Date
const { data } = await useFetch('/api/foo')
Função de serializador personalizada
Para personalizar o comportamento da serialização, você pode definir uma função toJSON
no seu objeto retornado. Se você definir um método toJSON
, o Nuxt respeitará o tipo de retorno da função e não tentará converter os tipos.
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
toJSON() {
return {
createdAt: {
year: this.createdAt.getFullYear(),
month: this.createdAt.getMonth(),
day: this.createdAt.getDate(),
},
}
},
}
return data
})
// O tipo de `data` é inferido como
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')
Usando um serializador alternativo
O Nuxt atualmente não suporta um serializador alternativo para JSON.stringify
. No entanto, você pode retornar seu payload como uma string normal e utilizar o método toJSON
para manter a segurança de tipo.
No exemplo abaixo, usamos superjson como nosso serializador.
import superjson from 'superjson'
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
// Contornar a conversão de tipo
toJSON() {
return this
}
}
// Serializar a saída para string, usando superjson
return superjson.stringify(data) as unknown as typeof data
})
import superjson from 'superjson'
// `date` é inferido como { createdAt: Date } e você pode usar com segurança os métodos do objeto Date
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
Receitas
Consumindo SSE (Eventos Enviados pelo Servidor) via requisição POST
Se você estiver consumindo SSE via requisição GET, pode usar EventSource
ou o composable VueUse useEventSource
.
Ao consumir SSE via requisição POST, você precisa lidar com a conexão manualmente. Veja como você pode fazer isso:
// Fazer uma requisição POST para o endpoint SSE
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
method: 'POST',
body: {
query: "Olá AI, como você está?",
},
responseType: 'stream',
})
// Criar um novo ReadableStream a partir da resposta com TextDecoderStream para obter os dados como texto
const reader = response.pipeThrough(new TextDecoderStream()).getReader()
// Ler o pedaço de dados à medida que o recebemos
while (true) {
const { value, done } = await reader.read()
if (done)
break
console.log('Recebido:', value)
}
Fazendo requisições paralelas
Quando as requisições não dependem umas das outras, você pode fazê-las em paralelo com Promise.all()
para aumentar o desempenho.
const { data } = await useAsyncData(() => {
return Promise.all([
$fetch("/api/comments/"),
$fetch("/api/author/12")
]);
});
const comments = computed(() => data.value?.[0]);
const author = computed(() => data.value?.[1]);
※Esta página é uma tradução não oficial da documentação oficial do Nuxt.js.
A página correspondente na documentação oficial está aqui:
https://nuxt.com/docs/3.x/getting-started/data-fetching