Nuxt 提供了两个可组合函数和一个内置库,用于在浏览器或服务器环境中执行数据获取:useFetch、useAsyncData 和 $fetch。
简而言之
$fetch 是发出网络请求最简单的方法。useFetch 是 $fetch 的一个封装,它在通用渲染中只获取一次数据。useAsyncData 类似于 useFetch,但提供了更细粒度的控制。我们将在最后几节中详细介绍 useFetch 和 useAsyncData 共享的一组通用选项和模式。
useFetch 和 useAsyncData 的必要性Nuxt 是一个可以在服务器和客户端环境中运行同构(或通用)代码的框架。如果在 Vue 组件的 setup 函数中使用 $fetch 函数 执行数据获取,这可能会导致数据被获取两次,一次在服务器上(用于渲染 HTML),另一次在客户端上(当 HTML 水合时)。这可能会导致水合问题,增加交互时间并导致不可预测的行为。
useFetch 和 useAsyncData 可组合函数通过确保如果 API 调用在服务器上进行,数据会在有效负载中转发到客户端来解决此问题。
有效负载是一个 JavaScript 对象,可通过 useNuxtApp().payload 访问。它在客户端上使用,以避免在代码在浏览器中水合期间执行时重新获取相同的数据。
<script setup lang="ts">
const { data } = await useFetch('/api/data')
async function handleFormSubmit () {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// My form data
},
})
}
</script>
<template>
<div v-if="data == undefined">
No data
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- form input tags -->
</form>
</div>
</template>
在上面的示例中,useFetch 将确保请求在服务器中发生并正确转发到浏览器。$fetch 没有这种机制,当请求仅从浏览器发出时,它是更好的选择。
Nuxt 在内部使用 Vue 的<Suspense>组件来防止在所有异步数据都可用于视图之前进行导航。数据获取可组合函数可以帮助您利用此功能,并根据每次调用选择最适合的方法。
<NuxtLoadingIndicator> 以在页面导航之间添加进度条。$fetchNuxt 包含ofetch库,并作为 $fetch 别名在您的应用程序中全局自动导入。
<script setup lang="ts">
async function addTodo () {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// My todo data
},
})
}
</script>
当在服务器上调用 useFetch 时,Nuxt 将使用 useRequestFetch 代理客户端头部和 Cookie(不应转发的头部除外,例如 host)。
<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
或者,以下示例展示了如何使用 useRequestHeaders 从服务器端请求(源自客户端)访问并将 Cookie 发送到 API。使用同构 $fetch 调用,我们确保 API 端点可以访问用户浏览器最初发送的相同 cookie 头部。仅当您不使用 useFetch 时才需要这样做。
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser () {
return await $fetch('/api/me', { headers })
}
</script>
useRequestFetch 自动将头部代理到调用。host, acceptcontent-length, content-md5, content-typex-forwarded-host, x-forwarded-port, x-forwarded-protocf-connecting-ip, cf-rayuseFetchuseFetch 可组合函数在内部使用 $fetch 在 setup 函数中进行 SSR 安全的网络调用。
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Page visits: {{ count }}</p>
</template>
此可组合函数是 useAsyncData 可组合函数和 $fetch 实用工具的封装。
useAsyncDatauseAsyncData 可组合函数负责封装异步逻辑并在解析后返回结果。
useFetch(url) 几乎等同于 useAsyncData(url, () => event.$fetch(url))。useRequestFetch中找到有关 event.fetch 的更多信息。)在某些情况下,使用 useFetch 可组合函数不合适,例如当 CMS 或第三方提供自己的查询层时。在这种情况下,您可以使用 useAsyncData 包装您的调用,并仍然保留可组合函数提供的好处。
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// This is also possible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
useAsyncData 的第一个参数是一个唯一的键,用于缓存第二个参数(查询函数)的响应。此键可以通过直接传递查询函数来忽略,键将自动生成。useAsyncData 实例的文件和行,因此建议始终创建自己的键以避免不必要的行为,例如在创建封装 useAsyncData 的自定义可组合函数时。useNuxtData 在组件之间共享相同数据或刷新特定数据很有用。<script setup lang="ts">
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})
</script>
useAsyncData 可组合函数是封装和等待多个 $fetch 请求完成然后处理结果的好方法。
<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async (_nuxtApp, { signal }) => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons', { signal }),
$fetch('/cart/offers', { signal }),
])
return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>
useAsyncData 用于获取和缓存数据,而不是触发副作用,例如调用 Pinia actions,因为这可能会导致意外行为,例如重复执行空值。如果您需要触发副作用,请使用 callOnce 实用工具来完成。<script setup lang="ts">
const offersStore = useOffersStore()
// you can't do this
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
useFetch 和 useAsyncData 具有下面列出的相同返回值。
data:传入的异步函数的结果。refresh/execute:一个函数,可用于刷新 handler 函数返回的数据。clear:一个函数,可用于将 data 设置为 undefined(或如果提供 options.default(),则设置为其值),将 error 设置为 undefined,将 status 设置为 idle,并标记所有当前待处理的请求为已取消。error:如果数据获取失败,则为错误对象。status:一个字符串,指示数据请求的状态("idle"、"pending"、"success"、"error")。data、error 和 status 是 Vue refs,可在 <script setup> 中通过 .value 访问默认情况下,Nuxt 会等待 refresh 完成才能再次执行。
server: false),那么在水合完成之前数据不会被获取。这意味着即使您在客户端等待 useFetch,data 在 <script setup> 中仍将为 null。useAsyncData 和 useFetch 返回相同的对象类型,并接受一组通用选项作为它们的最后一个参数。它们可以帮助您控制可组合函数的行为,例如导航阻塞、缓存或执行。
默认情况下,数据获取可组合函数将等待其异步函数解析完成后再通过 Vue 的 Suspense 导航到新页面。此功能可以通过 lazy 选项在客户端导航上忽略。在这种情况下,您必须使用 status 值手动处理加载状态。
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
lazy: true,
})
</script>
<template>
<!-- you will need to handle a loading state -->
<div v-if="status === 'pending'">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- do something -->
</div>
</div>
</template>
您也可以使用 useLazyFetch 和 useLazyAsyncData 作为便捷方法来执行相同的操作。
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
默认情况下,数据获取可组合函数将在客户端和服务器环境中执行其异步函数。将 server 选项设置为 false 可仅在客户端执行调用。在初始加载时,在水合完成之前数据不会被获取,因此您必须处理挂起状态,尽管在后续的客户端导航中,数据将在加载页面之前等待。
结合 lazy 选项,这对于不需要在首次渲染时获取的数据(例如,非 SEO 敏感数据)很有用。
/* This call is performed before hydration */
const articles = await useFetch('/api/article')
/* This call will only be performed on the client */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false,
})
useFetch 可组合函数旨在在 setup 方法中调用,或直接在生命周期钩子中的函数顶部级别调用,否则您应该使用 $fetch 方法。
pick 选项通过只选择您想要从可组合函数返回的字段来帮助您最小化存储在 HTML 文档中的有效负载大小。
<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description'],
})
</script>
<template>
<h1>{{ mountain.title }}</h1>
<p>{{ mountain.description }}</p>
</template>
如果您需要更多控制或映射多个对象,可以使用 transform 函数来改变查询结果。
const { data: mountains } = await useFetch('/api/mountains', {
transform: (mountains) => {
return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
},
})
pick 和 transform 都不会阻止不需要的数据被最初获取。但它们会阻止不需要的数据添加到从服务器传输到客户端的有效负载中。useFetch 和 useAsyncData 使用键来防止重复获取相同的数据。
useFetch 使用提供的 URL 作为键。或者,可以在作为最后一个参数传入的 options 对象中提供 key 值。useAsyncData 的第一个参数是字符串,则将其用作键。如果第一个参数是执行查询的处理函数,则会为您生成一个与 useAsyncData 实例的文件名和行号唯一关联的键。useNuxtData当多个组件使用 useAsyncData 或 useFetch 的相同键时,它们将共享相同的 data、error 和 status refs。这确保了组件之间的一致性,但要求某些选项保持一致。
以下选项必须在所有具有相同键的调用中保持一致
handler 函数deep 选项transform 函数pick 数组getCachedData 函数default 值// ❌ This will trigger a development warning
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: false })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: true })
以下选项可以安全地不同而不会触发警告
服务器lazyimmediatededupewatch// ✅ This is allowed
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: true })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: false })
如果您需要独立的实例,请使用不同的键
// These are completely independent instances
const { data: users1 } = useAsyncData('users-1', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
const { data: users2 } = useAsyncData('users-2', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
您可以使用计算引用、普通引用或 getter 函数作为键,从而实现动态数据获取,这些数据会在依赖项更改时自动更新
// Using a computed property as a key
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value),
)
// When userId changes, the data will be automatically refetched
// and the old data will be cleaned up if no other components use it
userId.value = '456'
如果您想手动获取或刷新数据,请使用可组合函数提供的 execute 或 refresh 函数。
<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>
<template>
<div>
<p>{{ data }}</p>
<button @click="() => refresh()">
Refresh data
</button>
</div>
</template>
execute 函数是 refresh 的别名,其工作方式完全相同,但在获取不立即时更具语义。
clearNuxtData 和 refreshNuxtData。如果您想清除提供的数据,无论出于何种原因,而不需要知道要传递给 clearNuxtData 的特定键,可以使用可组合函数提供的 clear 函数。
<script setup lang="ts">
const { data, clear } = await useFetch('/api/users')
const route = useRoute()
watch(() => route.path, (path) => {
if (path === '/') {
clear()
}
})
</script>
要在应用程序中其他响应式值更改时重新运行您的获取函数,请使用 watch 选项。您可以将其用于一个或多个可观察元素。
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* Changing the id will trigger a refetch */
watch: [id],
})
</script>
请注意,监听响应式值不会更改获取的 URL。例如,这将继续获取用户的相同初始 ID,因为 URL 是在函数调用时构建的。
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id],
})
</script>
如果您需要根据响应式值更改 URL,您可能希望使用计算 URL。
当提供了响应式获取选项时,它们将自动被监听并触发重新获取。在某些情况下,通过指定 watch: false 来选择退出此行为会很有用。
const id = ref(1)
// Won't automatically refetch when id changes
const { data, execute } = await useFetch('/api/users', {
query: { id }, // id is watched by default
watch: false, // disables automatic watching of id
})
// doesn't trigger refetch
id.value = 2
有时您可能需要从响应式值计算 URL,并在这些值每次更改时刷新数据。您不必费力地进行处理,而是可以将每个参数作为响应式值附加。Nuxt 将自动使用响应式值并在每次更改时重新获取数据。
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id,
},
})
</script>
在更复杂的 URL 构建情况下,您可以将回调用作计算 getter返回 URL 字符串。
每次依赖项更改时,数据都将使用新构建的 URL 获取。将其与不立即结合使用,您可以等待响应式元素更改后再获取数据。
<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>
<!-- disable the input while fetching -->
<input
v-model="id"
type="number"
:disabled="pending"
>
<div v-if="status === 'idle'">
Type an user ID
</div>
<div v-else-if="pending">
Loading ...
</div>
<div v-else>
{{ data }}
</div>
</div>
</template>
如果您需要强制刷新当其他响应式值更改时,您还可以监听其他值。
useFetch 可组合函数在被调用时就开始获取数据。您可以通过设置 immediate: false 来阻止这种情况,例如,等待用户交互。
这样,您将需要 status 来处理获取生命周期,并需要 execute 来启动数据获取。
<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">
Get data
</button>
</div>
<div v-else-if="status === 'pending'">
Loading comments...
</div>
<div v-else>
{{ data }}
</div>
</template>
为了进行更精细的控制,status 变量可以是
idlependingerrorsuccess当我们在浏览器中调用 $fetch 时,用户头部,如 cookie 将直接发送到 API。
通常,在服务器端渲染期间,由于安全考虑,$fetch 不会包含用户的浏览器 Cookie,也不会从获取响应中传递 Cookie。
然而,当在服务器上使用相对 URL 调用 useFetch 时,Nuxt 将使用 useRequestFetch 代理头部和 Cookie(不应转发的头部除外,例如 host)。
如果您想将 Cookie 从内部请求反向传递/代理回客户端,则需要自行处理。
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => {
/* Get the response from the server endpoint */
const res = await $fetch.raw(url)
/* Get the cookies from the response */
const cookies = res.headers.getSetCookie()
/* Attach each cookie to our incoming Request */
for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie)
}
/* Return the data of the response */
return res._data
}
<script setup lang="ts">
// This composable will automatically pass cookies to the client
const event = useRequestEvent()
const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
</script>
Nuxt 提供了一种在 Options API 中执行 asyncData 获取的方法。您必须将组件定义包装在 defineNuxtComponent 中才能使其工作。
<script>
export default defineNuxtComponent({
/* Use the fetchKey option to provide a unique key */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello'),
}
},
})
</script>
<script setup> 或 <script setup lang="ts"> 声明 Vue 组件。当使用 useAsyncData 和 useLazyAsyncData 将在服务器上获取的数据传输到客户端(以及其他任何利用Nuxt 有效负载的东西)时,有效负载使用devalue进行序列化。这使我们不仅可以传输基本的 JSON,还可以序列化和复活/反序列化更高级别的数据,例如正则表达式、日期、Map 和 Set、ref、reactive、shallowRef、shallowReactive 和 NuxtError 等等。
也可以为您不支持的类型定义自己的序列化器/反序列化器。您可以在 useNuxtApp 文档中阅读更多内容。
$fetch 或 useFetch 从服务器路由获取的数据 - 有关更多信息,请参阅下一节。当从 server 目录获取数据时,响应使用 JSON.stringify 进行序列化。但是,由于序列化仅限于 JavaScript 基本类型,Nuxt 会尽力转换 $fetch 和 useFetch 的返回类型以匹配实际值。
export default defineEventHandler(() => {
return new Date()
})
<script setup lang="ts">
// Type of `data` is inferred as string even though we returned a Date object
const { data } = await useFetch('/api/foo')
</script>
要自定义序列化行为,您可以在返回的对象上定义 toJSON 函数。如果您定义了 toJSON 方法,Nuxt 将尊重该函数的返回类型,并且不会尝试转换类型。
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
})
<script setup lang="ts">
// Type of `data` is inferred as
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')
</script>
Nuxt 目前不支持 JSON.stringify 的替代序列化器。但是,您可以将有效负载作为普通字符串返回,并利用 toJSON 方法来维护类型安全。
在下面的示例中,我们使用superjson作为我们的序列化器。
import superjson from 'superjson'
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
// Workaround the type conversion
toJSON () {
return this
},
}
// Serialize the output to string, using superjson
return superjson.stringify(data) as unknown as typeof data
})
<script setup lang="ts">
import superjson from 'superjson'
// `date` is inferred as { createdAt: Date } and you can safely use the Date object methods
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
</script>
EventSource或 VueUse 可组合函数useEventSource.当通过 POST 请求消费 SSE 时,您需要手动处理连接。操作方法如下
// Make a POST request to the SSE endpoint
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
method: 'POST',
body: {
query: 'Hello AI, how are you?',
},
responseType: 'stream',
})
// Create a new ReadableStream from the response with TextDecoderStream to get the data as text
const reader = response.pipeThrough(new TextDecoderStream()).getReader()
// Read the chunk of data as we get it
while (true) {
const { value, done } = await reader.read()
if (done) { break }
console.log('Received:', value)
}
当请求之间没有相互依赖时,您可以使用 Promise.all() 并行发出请求以提高性能。
const { data } = await useAsyncData((_nuxtApp, { signal }) => {
return Promise.all([
$fetch('/api/comments/', { signal }),
$fetch('/api/author/12', { signal }),
])
})
const comments = computed(() => data.value?.[0])
const author = computed(() => data.value?.[1])