---
title: "数据获取"
description: "Nuxt 提供了组合式函数来处理应用中的数据获取。"
canonical_url: "https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching"
---
# 数据获取

> Nuxt 提供了组合式函数来处理应用中的数据获取。

Nuxt 内置了两个组合式函数和一个库，可在浏览器或服务器环境中执行数据获取：`useFetch`、[`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 和 `$fetch`。

简而言之：

- [`$fetch`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/dollarfetch) 是发起网络请求最简单的方式。
- [`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 是 `$fetch` 的封装，仅在[通用渲染](https://nuxt.zhcndoc.com/docs/3.x/guide/concepts/rendering#universal-rendering)中获取一次数据。
- [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 与 `useFetch` 类似，但提供了更精细的控制。

`useFetch` 和 `useAsyncData` 都共享一组通用的选项和模式，我们将在最后的章节中详细介绍。

## 对 `useFetch` 和 `useAsyncData` 的需求

Nuxt 是一个框架，可以在服务器和客户端环境中运行同构（或通用）代码。如果在 Vue 组件的 setup 函数中使用 [`$fetch` 函数](https://nuxt.zhcndoc.com/docs/3.x/api/utils/dollarfetch)获取数据，可能会导致数据被获取两次：一次在服务器上（用于渲染 HTML），另一次在客户端上（HTML 水合时）。这可能会导致水合问题、增加交互时间，并引发不可预测的行为。

[`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 和 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 组合式函数通过确保在服务器上发起的 API 调用会将数据转发到客户端负载中，解决了这个问题。

负载是一个可通过 [`useNuxtApp().payload`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-nuxt-app#payload) 访问的 JavaScript 对象。在客户端，[水合期间](https://nuxt.zhcndoc.com/docs/3.x/guide/concepts/rendering#universal-rendering)代码于浏览器中执行时，它可用于避免重新获取相同的数据。

::tip
使用 [Nuxt DevTools](https://nuxt.zhcndoc.com/modules/devtools) 在 **Payload 标签页** 检查这些数据。
::

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

async function handleFormSubmit () {
  const res = await $fetch('/api/submit', {
    method: 'POST',
    body: {
      // 我的表单数据
    },
  })
}
</script>

<template>
  <div v-if="data == null">
    无数据
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- 表单输入标签 -->
    </form>
  </div>
</template>
```

在上面的示例中，`useFetch` 会确保请求发生在服务器端，并正确转发到浏览器。`$fetch` 没有此机制，更适合仅在浏览器端发起请求的场景。

### Suspense

Nuxt 底层使用 Vue 的 [`<Suspense>`](https://vue.zhcndoc.com/guide/built-ins/suspense) 组件阻止导航，直到所有异步数据可用于视图。数据获取组合函数可以帮助你利用此功能，并根据单次调用情况选择最合适的方式。

::note
你可以添加 [`<NuxtLoadingIndicator>`](https://nuxt.zhcndoc.com/docs/3.x/api/components/nuxt-loading-indicator)，在页面导航之间显示进度条。
::

## `$fetch`

Nuxt 包含了 [ofetch](https://github.com/unjs/ofetch) 库，并在全局自动导入成 `$fetch` 别名。

```vue twoslash [pages/todos.vue]
<script setup lang="ts">
async function addTodo () {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // 我的待办数据
    },
  })
}
</script>
```

::warning
请注意，仅使用 `$fetch` 不会提供[网络调用去重和导航阻止](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#the-need-for-usefetch-and-useasyncdata)。:br
建议在客户端交互（基于事件）中使用 `$fetch`，或在获取组件初始数据时与 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#useasyncdata) 搭配使用。
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/utils/dollarfetch"}
了解有关 `$fetch` 的更多信息。
::

### 向 API 传递客户端请求头

在服务器上调用 `useFetch` 时，Nuxt 会使用 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-request-fetch) 代理客户端请求头和 Cookie（但不会转发 `host` 等不应转发的请求头）。

```vue
<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
```

```ts
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
```

下面的示例则展示了如何使用 [`useRequestHeaders`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-request-headers)，在来自客户端的服务器端请求中访问 Cookie 并将其发送到 API。通过使用同构的 `$fetch` 调用，我们可以确保 API 端点能够访问用户浏览器最初发送的相同 `cookie` 请求头。只有在不使用 `useFetch` 时才需要这样做。

```vue
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])

async function getCurrentUser () {
  return await $fetch('/api/me', { headers })
}
</script>
```

::tip
你也可以使用 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-request-fetch) 自动代理请求头。
::

::caution
在代理请求头到外部 API 前请非常谨慎，并只包含所需请求头。并非所有请求头都适合被转发，可能会引入不期望的行为。以下是常见不应被代理的请求头列表：

- `host`，`accept`
- `content-length`，`content-md5`，`content-type`
- `x-forwarded-host`，`x-forwarded-port`，`x-forwarded-proto`
- `cf-connecting-ip`，`cf-ray`
::

## `useFetch`

[`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 组合式函数在底层使用 `$fetch`，在 setup 函数中发起 SSR 安全的网络调用。

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

<template>
  <p>页面访问次数：{{ count }}</p>
</template>
```

这个组合式函数封装了 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 组合式函数和 `$fetch` 工具。

::video-accordion{title="观看 Alexander Lichter 的视频，避免错误使用 useFetch" videoId="njsGVmcWviY"}
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch"}
::

::link-example{to="https://nuxt.zhcndoc.com/docs/examples/features/data-fetching"}
::

## `useAsyncData`

`useAsyncData` 组合函数负责包装异步逻辑，并在解析完成后返回结果。

::tip
`useFetch(url)` 几乎等同于 `useAsyncData(url, () => event.$fetch(url))`。:br
它是针对最常见用例的开发体验语法糖。（你可以在 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-request-fetch) 中了解更多关于 `event.fetch` 的信息。）
::

::video-accordion{title="观看 Alexander Lichter 的视频，深入了解 useFetch 和 useAsyncData 的区别" videoId="0X-aOpSGabA"}
::

在某些情况下，不适合使用 [`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 组合式函数，例如 CMS 或第三方服务提供了自己的查询层。这种情况下，你可以使用 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 包装调用，同时保留组合式函数带来的优势。

```vue [pages/users.vue]
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// 也可以这样写：
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
```

::note
[`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 的第一个参数是一个唯一键，用于缓存第二个参数（查询函数）的响应。直接传入查询函数时，可以省略此键，系统会自动生成。
:br :br
由于自动生成的键仅考虑了 `useAsyncData` 调用所在文件和行号，建议始终自定义唯一键以避免不期望的行为，尤其是当你创建自己的自定义组合函数包装 `useAsyncData` 时。
:br :br
设置键还可以用于通过 [`useNuxtData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-nuxt-data) 在组件之间共享相同的数据，或用于[刷新特定数据](https://nuxt.zhcndoc.com/docs/3.x/api/utils/refresh-nuxt-data#refresh-specific-data)。
::

```vue [pages/users/[id\\].vue]
<script setup lang="ts">
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})
</script>
```

`useAsyncData` 组合函数是包装并等待多个 `$fetch` 请求完成后处理结果的绝佳方式。

```vue
<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>
```

::note
`useAsyncData` 用于获取和缓存数据，而不是触发诸如调用 Pinia actions 之类的副作用，因为这可能导致重复执行等意外行为，例如使用空值重复执行。如果需要触发副作用，请使用 [`callOnce`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/call-once) 工具。

```vue
<script setup lang="ts">
const offersStore = useOffersStore()

// 不能这样做
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
```
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data"}
了解有关 `useAsyncData` 的更多信息。
::

## 返回值

`useFetch` 和 `useAsyncData` 返回相同的对象，包含如下内容：

- `data`：传入的异步函数结果。
- `refresh`/`execute`：用于刷新由 `handler` 函数返回的数据。
- `clear`：用于将 `data` 设为 `undefined`（或提供的 `options.default()` 的值），`error` 设为 `null`，`status` 设为 `idle`，并将当前所有待定请求标记为取消。
- `error`：数据获取失败时的错误对象。
- `status`：表示数据请求状态的字符串（`"idle"`、`"pending"`、`"success"`、`"error"`）。

::note
`data`、`error` 和 `status` 是 Vue 的 ref，在 `<script setup>` 中用 `.value` 访问。
::

默认情况下，Nuxt 会等待一次 `refresh` 执行完成后，才允许再次执行。

::note
如果你没有在服务器上获取数据（例如，使用 `server: false`），那么直到水合完成后才会获取数据。这意味着即使你在客户端等待 `useFetch`，`<script setup>` 中的 `data` 仍会是 undefined。
::

## 选项

[`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 和 [`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 返回相同类型的对象，并在最后一个参数中接受一组通用选项。这些选项可以帮助你控制组合式函数的行为，例如是否阻止导航、缓存或执行。

### 延迟加载

默认情况下，数据获取组合函数会等待其异步函数解析完成后，利用 Vue 的 Suspense 阻止页面导航。客户端导航时，可以通过 `lazy` 选项忽略此功能。此时，你需要手动使用 `status` 管理加载状态。

```vue twoslash [app.vue]
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
  lazy: true,
})
</script>

<template>
  <!-- 需要手动处理加载状态 -->
  <div v-if="status === 'pending'">
    加载中...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 操作内容 -->
    </div>
  </div>
</template>
```

你也可以使用 [`useLazyFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-lazy-fetch) 和 `useLazyAsyncData`，以便捷的方式实现相同功能。

```vue twoslash
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
```

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-lazy-fetch"}
了解有关 `useLazyFetch` 的更多信息。
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-lazy-async-data"}
了解有关 `useLazyAsyncData` 的更多信息。
::

::video-accordion{title="观看 Vue School 关于阻塞与非阻塞（延迟）请求的视频" videoId="1022000555" platform="vimeo"}
::

### 仅客户端获取

默认情况下，数据获取组合函数会在客户端和服务器端都执行异步函数。将 `server` 选项设为 `false` 则只在客户端执行。首次加载时，数据不会在水合完成前获取，因此必须处理等待状态，但随后客户端导航时会等待数据加载完毕才加载页面。

配合 `lazy` 选项，这对于首次渲染不需要数据（如非 SEO 敏感数据）非常有用。

```ts twoslash
/* 该调用在水合前执行 */
const articles = await useFetch('/api/article')

/* 该调用仅在客户端执行 */
const { status, data: comments } = useFetch('/api/comments', {
  lazy: true,
  server: false,
})
```

`useFetch` 组合式函数旨在 setup 方法中调用，或在生命周期钩子中的函数顶层直接调用；否则，你应该使用 [`$fetch` 方法](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#fetch)。

### 最小化负载大小

`pick` 选项可帮助你通过仅选择需要的字段，减小存储在 HTML 文档中的负载大小。

```vue
<script setup lang="ts">
/* 仅选择模板中使用的字段 */
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 }))
  },
})
```

::note
`pick` 和 `transform` 不阻止初始数据获取，只阻止不必要数据被加入服务器到客户端的负载中。
::

::video-accordion{title="观看 Vue School 关于减小负载大小的视频" videoId="1026410430" platform="vimeo"}
::

### 缓存与重新获取

#### 键

[`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 和 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 使用键来避免重复获取相同的数据。

- [`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 使用提供的 URL 作为键。或者，也可以在作为最后一个参数传入的 `options` 对象中提供 `key` 值。
- [`useAsyncData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-async-data) 在第一个参数为字符串时将其用作键。如果第一个参数是执行查询的 handler 函数，则会根据 `useAsyncData` 实例所在的文件名和行号为你生成一个唯一键。

::tip
要按键获取缓存数据，可以使用 [`useNuxtData`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-nuxt-data)
::

::video-accordion{title="观看 Vue School 关于使用 key 选项缓存数据的视频" videoId="1026410044" platform="vimeo"}
::

#### 共享状态与选项一致性

多个组件若使用相同的 `useAsyncData` 或 `useFetch` 键，将共享 `data`、`error` 和 `status` refs，保证状态一致。但这要求某些选项保持一致。

以下选项**必须在所有同键调用中保持一致**：

- `handler` 函数
- `deep` 选项
- `transform` 函数
- `pick` 数组
- `getCachedData` 函数
- `default` 值

```ts
// ❌ 会触发开发警告
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 })
```

以下选项**可安全不同**且不会触发警告：

- `server`
- `lazy`
- `immediate`
- `dedupe`
- `watch`

```ts
// ✅ 允许
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 })
```

若需要独立实例，请使用不同键：

```ts
// 彼此完全独立的实例
const { data: users1 } = useAsyncData('users-1', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
const { data: users2 } = useAsyncData('users-2', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
```

#### 响应式键

你可以使用计算属性 ref、普通 ref 或 getter 函数作为键，实现依赖变化时自动重新获取数据：

```ts
// 使用计算属性作为键
const userId = ref('123')
const { data: user } = useAsyncData(
  computed(() => `user-${userId.value}`),
  () => fetchUser(userId.value),
)

// 当 userId 变化时数据会自动刷新，且若无其他组件使用旧数据则被清理
userId.value = '456'
```

#### refresh 和 execute

如果想手动获取或刷新数据，可使用组合函数提供的 `execute` 或 `refresh` 函数。

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

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">
      刷新数据
    </button>
  </div>
</template>
```

`execute` 函数是 `refresh` 的别名，工作方式完全相同，但在获取[非即时](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#not-immediate)时语义更明确。

::tip
要全局重新获取或使缓存数据失效，请参阅 [`clearNuxtData`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/clear-nuxt-data) 和 [`refreshNuxtData`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/refresh-nuxt-data)。
::

#### 清理

若想清理已有数据而不必使用特定键调用 `clearNuxtData`，组合函数提供了 `clear` 函数。

```vue twoslash
<script setup lang="ts">
const { data, clear } = await useFetch('/api/users')

const route = useRoute()
watch(() => route.path, (path) => {
  if (path === '/') {
    clear()
  }
})
</script>
```

#### 监听

为让每次应用中其他响应式值变动时重新执行数据获取函数，可以使用 `watch` 选项。可以传入一个或多个可监听元素。

```vue twoslash
<script setup lang="ts">
const id = ref(1)

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

注意：**监听响应式值不会改变被获取的 URL**。例如，下面代码会一直请求初始的 user id：

```vue
<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id],
})
</script>
```

如果需要根据响应式值更改 URL，可以改用[计算 URL](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#computed-url)。

提供响应式获取选项时，Nuxt 会自动监听它们并触发重新获取。在某些情况下，可以通过指定 `watch: false` 来选择不采用此行为。

```ts
const id = ref(1)

// 当 id 变化时不会自动重新获取
const { data, execute } = await useFetch('/api/users', {
  query: { id }, // 默认会监听 id
  watch: false, // 禁用对 id 的自动监听
})

// 不会触发重新获取
id.value = 2
```

#### 计算 URL

有时你可能需要根据响应式值计算一个 URL，并在每次这些值变化时刷新数据。你无需费力处理，只需将每个参数作为响应式值附加即可。Nuxt 会自动使用响应式值，并在其变化时重新获取数据。

```vue
<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id,
  },
})
</script>
```

若 URL 逻辑复杂，也可以使用回调函数作为[计算 getter](https://vue.zhcndoc.com/guide/essentials/computed.html)返回 URL 字符串。

每当依赖项发生变化时，都会使用新构造的 URL 获取数据。将此功能与[非即时](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#not-immediate)获取结合使用，你可以等到响应式元素发生变化后再获取数据。

```vue
<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>
    <!-- 加载时禁用输入 -->
    <input
      v-model="id"
      type="number"
      :disabled="pending"
    >

    <div v-if="status === 'idle'">
      请输入用户 ID
    </div>

    <div v-else-if="pending">
      加载中...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>
```

如果需要在其他响应式值变化时强制刷新，也可以[监听其他值](https://nuxt.zhcndoc.com/docs/3.x/getting-started/data-fetching#watch)。

### 非即时

`useFetch` 组合函数默认调用时即开始获取数据。设置 `immediate: false` 可阻止，适用于等待用户操作后再获取。

此时需用 `status` 管理状态，用 `execute` 启动获取。

```vue
<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">
      获取数据
    </button>
  </div>

  <div v-else-if="status === 'pending'">
    评论加载中...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>
```

`status` 对应的状态有：

- `idle`：请求尚未启动
- `pending`：请求已启动，未完成
- `error`：请求失败
- `success`：请求成功完成

## 传递请求头和 Cookie

在浏览器执行 `$fetch` 时，用户的请求头（如 cookie）会直接发送给 API。

通常在服务器端渲染时，出于安全原因，`$fetch` 不会携带用户浏览器的 Cookie，也不会传递响应中的 Cookie。

但是，在服务器上使用相对 URL 调用 `useFetch` 时，Nuxt 会使用 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-request-fetch) 代理请求头和 Cookie（但不会转发 `host` 等不应转发的请求头）。

### 服务端 API 调用响应时传递 Cookie

如果你想把 Cookie 从内部请求传递回客户端，需要自行处理。

```ts [composables/fetch.ts]
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* 获取服务器端点响应 */
  const res = await $fetch.raw(url)
  /* 从响应中获取 Cookies */
  const cookies = res.headers.getSetCookie()
  /* 将每个 cookie 附加到入站请求 */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* 返回响应数据 */
  return res._data
}
```

```vue
<script setup lang="ts">
// 该组合函数会自动将 Cookie 传递给客户端
const event = useRequestEvent()

const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))

onMounted(() => console.log(document.cookie))
</script>
```

## 选项 API 支持

Nuxt 提供在 Options API 中执行 `asyncData` 获取数据的方式。使用时需将组件定义包裹在 `defineNuxtComponent` 中。

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

::note
推荐在 Nuxt 中使用 `<script setup>` 或 `<script setup lang="ts">` 来声明 Vue 组件。
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/utils/define-nuxt-component"}
::

## 服务器到客户端的数据序列化

使用 `useAsyncData` 和 `useLazyAsyncData` 将服务器上获取的数据传输到客户端时（以及使用[Nuxt 负载](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-nuxt-app#payload)的其他功能），负载会使用 [`devalue`](https://github.com/sveltejs/devalue) 进行序列化。这不仅允许我们传输基本的 JSON，还可以序列化并恢复／反序列化更高级的数据类型，例如正则表达式、Dates、Map 和 Set、`ref`、`reactive`、`shallowRef`、`shallowReactive` 和 `NuxtError` 等。

你也可以为 Nuxt 不支持的类型定义自己的序列化器／反序列化器。更多信息请参阅 [`useNuxtApp`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-nuxt-app#payload) 文档。

::note
注意，此功能**不适用于**使用 `$fetch` 或 `useFetch` 从服务器路由获取数据后传入的内容——详见下一节。
::

## API 路由中的数据序列化

从 `server` 目录获取数据时，响应会使用 `JSON.stringify` 进行序列化。然而，由于序列化仅限于 JavaScript 原始类型，Nuxt 会尽力将 `$fetch` 和 [`useFetch`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-fetch) 的返回类型转换为与实际值相匹配的类型。

::read-more{icon="i-simple-icons-mdnwebdocs" to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description" target="_blank"}
了解更多关于 `JSON.stringify` 的限制。
::

### 示例

```ts [server/api/foo.ts]
export default defineEventHandler(() => {
  return new Date()
})
```

```vue [app.vue]
<script setup lang="ts">
// 尽管返回的是 Date 对象，`data` 推断类型为 string
const { data } = await useFetch('/api/foo')
</script>
```

### 自定义序列化函数

要自定义序列化行为，可以在返回对象中定义 `toJSON` 函数。定义后，Nuxt 会尊重该函数返回类型，不做额外转换。

```ts [server/api/bar.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
})
```

```vue [app.vue]
<script setup lang="ts">
// `data` 推断为
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')
</script>
```

### 使用替代序列化器

目前 Nuxt 不支持替代 `JSON.stringify` 的序列化器。但你可以返回字符串形式的负载，并利用 `toJSON` 方法保持类型安全。

下例中使用 [superjson](https://github.com/blitz-js/superjson) 作为序列化工具。

```ts [server/api/superjson.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
})
```

```vue [app.vue]
<script setup lang="ts">
import superjson from 'superjson'

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

## 使用示例

### 通过 POST 请求消费 SSE（服务器推送事件）

::tip
若通过 GET 请求消费 SSE，可使用 [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) 或 VueUse 组合式函数 [`useEventSource`](https://vueuse.org/core/useeventsource/)。
::

通过 POST 请求消费 SSE 时，需要手动处理连接。示例代码如下：

```ts
// 向 SSE 端点发起 POST 请求
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: 'Hello AI, how are you?',
  },
  responseType: 'stream',
})

// 创建一个新的 ReadableStream，并通过 TextDecoderStream 转成文本
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

// 逐块读取数据
while (true) {
  const { value, done } = await reader.read()

  if (done) { break }

  console.log('收到数据:', value)
}
```

### 并行请求

当多个请求彼此独立时，可以使用 `Promise.all()` 并行执行，提升性能。

```ts
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])
```

::video-accordion{title="观看 Vue School 关于并行数据获取的视频" videoId="1024262536" platform="vimeo"}
::


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
