数据获取 Data Fetching

Nuxt 提供可组合项来处理应用程序中的数据获取。

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

它们一起使用,可确保跨环境兼容性和高效缓存,并避免重复的网络调用。

useFetch 是在 Nuxt 中执行 API 调用的最直接的方法。

如果需要更细粒度的控制,可以单独使用useAsyncData$fetch

这两个可组合项共享一组通用的选项和模式,我们将在最后几节中详细介绍这些选项和模式。

为什么要使用特定的可组合项?

当使用像 Nuxt 这样可以在客户端和服务器环境上执行调用和渲染页面的框架时,必须解决一些挑战。 这就是 Nuxt 提供可组合项来包装查询的原因。

网络调用重复

useFetchuseAsyncData 可组合项确保一旦在服务器上进行 API 调用,数据就会在有效负载中正确转发到客户端。 该 JavaScript 对象可通过window.__NUXT__访问,并在客户端上使用,以避免在浏览器中执行代码时重新获取相同的数据。

使用 Nuxt DevTools 在有效负载选项卡中检查此数据。

有效的缓存

useFetchuseAsyncData 都使用密钥来缓存 API 响应并进一步减少 API 调用。 稍后我们将详细介绍如何使该缓存失效。

Suspense

Nuxt 在底层使用 Vue 的 <Suspense> 组件来防止在每个异步数据可供视图使用之前进行导航。 数据获取可组合项可以帮助您利用此功能并根据每次调用使用最适合的功能。

这些可组合项是自动导入的,可以在setup函数或生命周期挂钩中使用

useFetch

useFetch 是执行数据获取的最直接的方法。 它是useAsyncData可组合项和$fetch实用程序的包装器。

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

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

$fetch

ofetch 库构建在 fetch API 之上,并为其添加了方便的功能:

  • 在浏览器、节点或工作环境中以相同的方式工作
  • 自动响应解析
  • 错误处理
  • 自动重试
  • 拦截器

ofetch 由 Nuxt 自动导入并由 useFetch 可组合项使用。

它还可以通过$fetch别名在整个应用程序中使用:

ts
const users = await $fetch('/api/users').catch(error => error.data)

请注意,仅使用 $fetch 不会提供本页第一部分 中描述的好处。 建议在将数据发布到事件处理程序、执行仅客户端逻辑时使用$fetch,或与useAsyncData结合使用。

useAsyncData

useFetch 接收 URL 并获取该数据,而 useAsyncData 可能有更复杂的逻辑 。 useFetch(url) 几乎等同于 useAsyncData(url, () => $fetch(url)) - 它是最常见用例的开发人员体验糖。

在某些情况下,使用 useFetch 可组合项并不合适,例如,当 CMS 或第三方提供自己的查询层时。 在这种情况下,您可以使用 useAsyncData 来包装您的调用,并仍然保留可组合项提供的好处:

ts
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

useAsyncData 的第一个参数是用于缓存第二个参数(查询函数)的响应的唯一键。 通过直接传递查询函数可以忽略该参数。 在这种情况下,它将自动生成。

Options

useAsyncDatauseFetch 返回相同的对象类型并接受一组通用选项 他们最后的争论。 它们可以帮助您控制可组合项的行为,例如导航阻止、缓存或执行。

Lazy

默认情况下,数据获取可组合项将等待其异步函数的解析,然后使用 Vue 的 Suspense 导航到新页面。 可以使用lazy选项在客户端导航上忽略此功能。 在这种情况下,您将必须使用pending值手动处理加载状态。

app.vue
vue
<script setup>
const { pending, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- you will need to handle a loading state -->
  <div v-if="pending">
    Loading ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- do something -->
    </div>
  </div>
</template>

您也可以使用 useLazyFetchuseLazyAsyncData 作为执行相同操作的便捷方法。

ts
const { pending, data: posts } = useLazyFetch('/api/posts')

Client-only fetching

默认情况下,数据获取可组合项将在客户端和服务器环境上执行其异步功能。 将server选项设置为false以仅在客户端执行调用。 与lazy选项结合使用,这对于第一次渲染时不需要的数据(例如,非 SEO 敏感数据)非常有用。

ts
/* 该调用只会在客户端执行 */
const { pending, data: posts } = useFetch('/api/comments', {
  lazy: true,
  server: false
})

Minimize payload size

pick选项通过仅选择您想要从可组合项返回的字段,帮助您最大限度地减少 HTML 文档中存储的有效负载大小。

vue
<script setup>
/* 仅选择模板中使用的字段 */
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

如果您需要对多个对象进行更多控制或映射,可以使用transform函数来更改查询结果。

ts
const { data: mountains } = await useFetch('/api/mountains', {
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})

Caching and refetching

Keys

useFetchuseAsyncData 使用键来防止重新获取相同的数据。

  • useFetch 使用提供的 URL 作为键。 或者,可以在作为最后一个参数传递的options对象中提供key值。
  • useAsyncData 如果它是字符串,则使用其第一个参数作为键。 如果第一个参数是执行查询的处理函数,那么将为您生成一个对于useAsyncData实例的文件名和行号唯一的键。

要按键获取缓存数据,可以使用 useNuxtData

Refresh and execute

如果您想手动获取或刷新数据,请使用可组合项提供的executerefresh函数。 (executerefresh的别名,其工作方式完全相同,但对于immediate: false的情况更具语义)。

vue
<script setup>
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="refresh">
      Refresh data
    </button>
  </div>
</template>

要全局重新获取或使缓存数据无效,请参阅 clearNuxtDatarefreshNuxtData

Watch

要在应用程序中的其他无功值每次发生变化时重新运行提取函数,请使用watch选项。

ts
const { data, error, refresh } = await useFetch('/api/users', {
  /* 改变id会触发重新获取 */
  watch: [id]
})

const id = ref(1)

当我们在浏览器中调用 $fetch 时,像 cookie 这样的用户标头将被直接发送到 API。 但在服务器端渲染期间,由于“$fetch”请求在服务器“内部”发生,因此它不包含用户的浏览器 cookie,也不传递来自 fetch 响应的 cookie。

将客户端标头传递给 API

我们可以使用 useRequestHeaders 从服务器端访问 cookie 并将其代理到 API。

下面的示例将请求标头添加到同构的$fetch调用中,以确保 API 端点可以访问用户最初发送的相同cookie标头。

vue
<script setup>
const headers = useRequestHeaders(['cookie'])
const { data } = await useFetch('/api/me', { headers })
</script>

在将标头代理到外部 API 之前要非常小心,并且只包含您需要的标头。 并非所有标头都可以安全地被绕过,并且可能会引入不需要的行为。 以下是不被代理的常见标头的列表:

  • host, accept
  • content-length, content-md5, content-type
  • x-forwarded-host, x-forwarded-port, x-forwarded-proto
  • cf-connecting-ip, cf-ray

如果您想从另一个方向传递/代理cookie,从内部请求返回到客户端,您将需要自己处理这个问题。

composables/fetch.ts
ts
import { H3Event, appendResponseHeader } from 'h3'

export async function fetchWithCookie(event: H3Event, url: string) {
  const res = await $fetch.raw(url)
  const cookies = (res.headers.get('set-cookie') || '').split(',')
  for (const cookie of cookies)
    appendResponseHeader(event, 'set-cookie', cookie)

  return res._data
}
vue
<script setup lang="ts">
// 该可组合项会自动将 cookie 传递给客户端
const event = useRequestEvent()
const result = await fetchWithCookie(event, '/api/with-cookie')
onMounted(() => console.log(document.cookie))
</script>

选项 API 支持

Nuxt 3 提供了一种在选项 API 中执行asyncData获取的方法。 您必须将组件定义包装在defineNuxtComponent中才能正常工作。

vue
<script>
export default defineNuxtComponent({
  /* 使用 fetchKey 选项提供唯一的密钥 */
  fetchKey: 'hello',
  async asyncData() {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})
</script>

使用 <script setup lang="ts"> 是在 Nuxt 3 中声明 Vue 组件的推荐方法。

序列化

server目录获取数据时,使用JSON.stringify对响应进行序列化。 然而,由于序列化仅限于 JavaScript 基元类型,Nuxt 会尽力转换 $fetchuseFetch 的返回类型以匹配实际值。

您可以在此处了解有关JSON.stringify限制的更多信息。

Example

server/api/foo.ts
ts
export default defineEventHandler(() => {
  return new Date()
})
app.vue
vue
<script setup lang="ts">
// 即使我们返回了 Date 对象,“data”的类型也被推断为字符串
const { data } = await useFetch('/api/foo')
</script>

自定义序列化器函数

要自定义序列化行为,您可以在返回的对象上定义“toJSON”函数。 如果您定义了一个“toJSON”方法,Nuxt 将尊重该函数的返回类型,并且不会尝试转换类型。

server/api/bar.ts
ts
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
})
app.vue
vue
<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 作为序列化器。

server/api/superjson.ts
ts
import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // 解决类型转换问题
    toJSON() {
      return this
    }
  }

  // 使用 superjson 将输出序列化为字符串
  return superjson.stringify(data) as unknown as typeof data
})
app.vue
vue
<script setup lang="ts">
import superjson from 'superjson'

// `date` 被推断为 { createdAt: Date } 并且您可以安全地使用 Date 对象方法
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
})
</script>