跳到主要内容

数据获取

Nuxt 提供了用于处理应用内数据获取的 composables。

Nuxt 自带两个 composables 和一个内置库,用于在浏览器或服务端环境中执行数据获取:useFetchuseAsyncData$fetch

简而言之:

useFetchuseAsyncData 共享一组通用的选项和模式,我们将在后面的小节中详述。

为何需要 useFetchuseAsyncData

Nuxt 是一个能够在服务端和客户端环境中运行同构(或通用)代码的框架。如果在 Vue 组件的 setup 函数中使用 $fetch 函数 来执行数据获取,可能会导致数据被获取两次:一次在服务端(用于渲染 HTML),一次在客户端(当 HTML 被 hydration 时)。这可能导致 hydration 问题、增加可交互时间,并引发不可预测的行为。

useFetchuseAsyncData 这两个 composables 通过确保:如果在服务端发起了 API 调用,数据会被转发到客户端(在 payload 中),从而解决这个问题。

payload 是一个可以通过 useNuxtApp().payload 访问的 JavaScript 对象。它在客户端被用来避免在代码于浏览器中执行时进行 hydration 期间重新获取相同的数据。

使用 Nuxt DevToolsPayload 标签页中检查这些数据。
app/app.vue
<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 的说明

本文档中的示例通常会 awaituseFetchuseAsyncData 的调用,但这并不总是必需的。

await 不会改变服务端渲染的 HTML。在服务端渲染期间,无论哪种方式,Nuxt 都会等待请求解析后再序列化页面(底层是 <Suspense>onServerPrefetch),因此始终会向浏览器发送完整填充的结果。

await 改变的是你自己的 <script setup> 中接下来发生的事情,以及客户端导航的行为:

  • 使用 await 时,执行会暂停,直到数据就绪,因此调用之后的任何代码都可以依赖 data 已经被填充。在客户端导航时,这会阻塞导航直到数据解析完成:用户停留在当前页面(可选地带有一个 <NuxtLoadingIndicator>),然后跳转到一个已经完整填充的页面。这是默认行为。
  • 不使用 await 时,执行会立即继续,而请求在后台运行,因此 data 以默认值开始,并在请求解析后填充。在客户端导航时,这意味着导航会立即发生,你需要自己处理加载和错误状态,通常通过返回的 statuserror refs。

两种方式并非绝对的好坏之分;正确的选择取决于你希望该路由拥有怎样的体验。

不 await 在用户可见效果上类似于 lazy 选项(不会阻塞导航,你自己处理加载状态),但两者并不相同:lazy 是一个显式标志,会将请求推迟到组件挂载时,而仅仅不 await 是在 setup 期间就发起请求。当你想要非阻塞行为时,推荐使用 lazy(或 useLazyFetch / useLazyAsyncData),因为它使意图更明确。

awaitlazy 是相互独立的,在客户端 await 一个 lazy 函数不会产生你期望的效果。如果你 await 一个 lazy 调用(例如 await useLazyFetch(...)await useFetch(..., { lazy: true })),它仍然会像往常一样阻塞服务端渲染,但在客户端导航时,await 会立即解析而不等待请求。dataawait 之后仍然会是它的默认值,你必须通过 status 处理加载状态。如果你确实希望导航等待数据,请去掉 lazy 选项,而不是依赖 await

$fetch

Nuxt 内置了 ofetch 库,并在整个应用中以 $fetch 别名全局自动导入。

pages/todos.vue
<script setup lang="ts">
async function addTodo () {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // My todo data
    },
  })
}
</script>
注意,仅使用 $fetch 不会提供网络请求去重和导航阻止。:br 推荐将 $fetch 用于客户端交互(基于事件),或者在与 useAsyncData 结合时用于获取组件的初始数据。

阅读更多关于 $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 来自动将请求头代理到调用中。
在将请求头代理到外部 API 之前要非常小心,只包含你需要的请求头。并非所有请求头都适合被绕过,可能会引入不期望的行为。以下是不应被代理的常见请求头列表:
  • hostaccept
  • content-lengthcontent-md5content-type
  • x-forwarded-hostx-forwarded-portx-forwarded-proto
  • cf-connecting-ipcf-ray

useFetch

useFetch composable 在底层使用 $fetch 来在 setup 函数中发起 SSR 安全的网络调用。

app/app.vue
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>

<template>
  <p>Page visits: {{ count }}</p>
</template>

这个 composable 是对 useAsyncData composable 和 $fetch 工具的封装。