数据获取
Nuxt 提供了用于处理应用内数据获取的 composables。
Nuxt 自带两个 composables 和一个内置库,用于在浏览器或服务端环境中执行数据获取:useFetch、useAsyncData 和 $fetch。
简而言之:
$fetch是发起网络请求最简单的方式。useFetch是对$fetch的封装,在统一渲染(universal rendering)中只获取数据一次。useAsyncData与useFetch类似,但提供了更细粒度的控制。
useFetch 和 useAsyncData 共享一组通用的选项和模式,我们将在后面的小节中详述。
为何需要 useFetch 和 useAsyncData
Nuxt 是一个能够在服务端和客户端环境中运行同构(或通用)代码的框架。如果在 Vue 组件的 setup 函数中使用 $fetch 函数 来执行数据获取,可能会导致数据被获取两次:一次在服务端(用于渲染 HTML),一次在客户端(当 HTML 被 hydration 时)。这可能导致 hydration 问题、增加可交互时间,并引发不可预测的行为。
useFetch 和 useAsyncData 这两个 composables 通过确保:如果在服务端发起了 API 调用,数据会被转发到客户端(在 payload 中),从而解决这个问题。
payload 是一个可以通过 useNuxtApp().payload 访问的 JavaScript 对象。它在客户端被用来避免在代码于浏览器中执行时进行 hydration 期间重新获取相同的数据。
<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 没有这种机制,当请求仅从浏览器发起时,它是更好的选择。
Suspense
Nuxt 在底层使用了 Vue 的 <Suspense> 组件,以防止在视图所需的每个异步数据都可用之前进行导航。数据获取 composables 可以帮助你利用这个特性,并视情况选择每个调用最合适的方式。
<NuxtLoadingIndicator> 来在页面导航之间添加进度条。关于 await 的说明
本文档中的示例通常会 await 对 useFetch 和 useAsyncData 的调用,但这并不总是必需的。
await 不会改变服务端渲染的 HTML。在服务端渲染期间,无论哪种方式,Nuxt 都会等待请求解析后再序列化页面(底层是 <Suspense> 和 onServerPrefetch),因此始终会向浏览器发送完整填充的结果。
await 改变的是你自己的 <script setup> 中接下来发生的事情,以及客户端导航的行为:
- 使用
await时,执行会暂停,直到数据就绪,因此调用之后的任何代码都可以依赖data已经被填充。在客户端导航时,这会阻塞导航直到数据解析完成:用户停留在当前页面(可选地带有一个<NuxtLoadingIndicator>),然后跳转到一个已经完整填充的页面。这是默认行为。 - 不使用
await时,执行会立即继续,而请求在后台运行,因此data以默认值开始,并在请求解析后填充。在客户端导航时,这意味着导航会立即发生,你需要自己处理加载和错误状态,通常通过返回的status和errorrefs。
两种方式并非绝对的好坏之分;正确的选择取决于你希望该路由拥有怎样的体验。
不 await 在用户可见效果上类似于 lazy 选项(不会阻塞导航,你自己处理加载状态),但两者并不相同:lazy 是一个显式标志,会将请求推迟到组件挂载时,而仅仅不 await 是在 setup 期间就发起请求。当你想要非阻塞行为时,推荐使用 lazy(或 useLazyFetch / useLazyAsyncData),因为它使意图更明确。
await 和 lazy 是相互独立的,在客户端 await 一个 lazy 函数不会产生你期望的效果。如果你 await 一个 lazy 调用(例如 await useLazyFetch(...) 或 await useFetch(..., { lazy: true })),它仍然会像往常一样阻塞服务端渲染,但在客户端导航时,await 会立即解析而不等待请求。data 在 await 之后仍然会是它的默认值,你必须通过 status 处理加载状态。如果你确实希望导航等待数据,请去掉 lazy 选项,而不是依赖 await。$fetch
Nuxt 内置了 ofetch 库,并在整个应用中以 $fetch 别名全局自动导入。
<script setup lang="ts">
async function addTodo () {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// My todo data
},
})
}
</script>
阅读更多关于 $fetch 的内容。
将客户端请求头传递给 API
在服务端调用 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-ray
useFetch
useFetch composable 在底层使用 $fetch 来在 setup 函数中发起 SSR 安全的网络调用。
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Page visits: {{ count }}</p>
</template>
这个 composable 是对 useAsyncData composable 和 $fetch 工具的封装。