---
title: "数据获取"
description: "Nuxt 为您的应用提供了用于处理数据获取的 composables。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.x/getting-started/data-fetching"
---
# 数据获取

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

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

简而言之：

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

`useFetch` 和 `useAsyncData` 都共享一组通用的选项和模式，我们将在最后几节详细说明。

## `useFetch` 和 `useAsyncData` 的必要性

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

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

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

<tip>

使用 [Nuxt DevTools](https://devtools.nuxt.com) 在 **Payload 选项卡** 中检查这些数据。

</tip>

```vue [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: {
      // 我的表单数据
    },
  })
}
</script>

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

在上面的示例中，`useFetch` 会确保请求在服务器上执行，并正确转发到浏览器。`$fetch` 没有这样的机制，因此当请求完全由浏览器发起时，使用它是更好的选择。

### Suspense

Nuxt 在内部使用 Vue 的 [`<Suspense>`](https://vue.zhcndoc.com/guide/built-ins/suspense) 组件，以防止在所有异步数据可用于视图之前进行导航。数据获取的 composables 可以帮助您利用此功能，并可按调用情况选择最适合的方案。

<note>

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

</note>

### 关于 `await` 的说明

本文档中的示例通常会 `await` 调用 `useFetch` 和 `useAsyncData`，但这并非总是必需的。

`await` **不会**改变服务器渲染的 HTML。在服务器渲染期间，Nuxt 无论如何都会在序列化页面之前等待请求完成（底层依赖 `<Suspense>` 和 `onServerPrefetch`），因此完整填充的数据结果总是会发送到浏览器。

`await` *真正* 改变的是在你自己的 `<script setup>` 中接下来会发生什么，以及客户端导航的行为：

- **使用 await 时**，执行会暂停，直到数据准备就绪，因此调用之后的代码可以依赖已经填充的 `data`。在客户端导航时，这会阻止导航，直到数据解析完成：用户会停留在当前页面（可选择显示 [`<NuxtLoadingIndicator>`](https://nuxt.zhcndoc.com/docs/4.x/api/components/nuxt-loading-indicator)），然后进入数据已完全填充的页面。这是默认行为。
- **不使用 await 时**，执行会立即继续，而请求在后台运行，因此 `data` 会以默认值开始，并在请求解析后填充。在客户端导航时，导航会立即发生，而你需要负责处理加载和错误状态，通常通过返回的 `status` 和 `error` refs 实现。

没有一种方式在所有场景下都更好；正确的选择取决于你希望该路由呈现怎样的体验。

不使用 await 与 [`lazy`](#lazy) 选项的用户可见效果类似（不会阻止导航，并且需要自行处理加载状态），但两者并不完全相同：`lazy` 是一个显式标记，会将请求推迟到组件挂载时执行，而仅仅不使用 await 则会在 setup 期间启动请求。如果你希望采用非阻塞行为，建议使用 `lazy`（或 [`useLazyFetch`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-lazy-fetch) / [`useLazyAsyncData`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-lazy-async-data)），因为这样可以明确表达意图。

<warning>

`await` 和 `lazy` 是独立的，在客户端等待一个 `lazy` 函数不会产生你期望的效果。如果你 `await` 一个 `lazy` 调用（例如 `await useLazyFetch(...)` 或 `await useFetch(..., { lazy: true })`），它仍然会像往常一样阻塞服务器渲染，但在 **客户端导航** 中，`await` 会立即解析，而不会等待请求完成。`data` 在 `await` 之后仍然会保持其默认值，你必须通过 `status` 处理加载状态。如果你确实想让导航等待数据，请去掉 `lazy` 选项，而不是依赖 `await`。

</warning>

## `$fetch`

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

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

<warning>

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

</warning>

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/utils/dollarfetch">

详细了解 `$fetch`。

</read-more>

### 为持有 `$fetch` 的值指定类型

要为持有 `$fetch` 本身的值指定类型（例如传入插件或通过 `provide`/`inject` 提供的 `$fetch` 实例），请使用 `$Fetch` 类型（`TypedFetch` 的别名）：

```ts
import type { $Fetch } from '#app'

function createClient (fetcher: $Fetch) {
  return {
    todos: () => fetcher('/api/todos'),
  }
}
```

### 将客户端请求头传递给 API

在服务器上调用 `useFetch` 时，Nuxt 会使用 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-request-fetch) 代理客户端请求头和 cookies（不包括不应转发的请求头，例如 `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/4.x/api/composables/use-request-headers) 在服务端请求（由客户端发起）中访问 cookies，并将其发送给 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/4.x/api/composables/use-request-fetch) 自动代理请求头。

</tip>

<caution>

在将头代理到外部 API 之前请务必谨慎，仅包含您需要的头。并非所有头都可以安全地被绕过，可能会引入不期望的行为。以下是一些通常不应被代理的常见头列表：

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

</caution>

## `useFetch`

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

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

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

这个 composable 是对 [`useAsyncData`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-async-data) composable 和 `$fetch` 工具的封装。

<video-accordion title="观看 Alexander Lichter 的视频，避免错误使用 useFetch" video-id="njsGVmcWviY">



</video-accordion>

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-fetch">



</read-more>

<link-example to="https://nuxt.zhcndoc.com/docs/4.x/examples/features/data-fetching">



</link-example>

## `useAsyncData`

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

<tip>

`useFetch(url)` 几乎等同于 `useAsyncData(url, () => event.$fetch(url))`。:br
这是针对最常见用例提供的开发体验语法糖。（有关 `event.fetch` 的更多信息，请参阅 [`useRequestFetch`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-request-fetch)。）

</tip>

<video-accordion title="观看 Alexander Lichter 的视频，深入了解 useFetch 与 useAsyncData 的区别" video-id="0X-aOpSGabA">



</video-accordion>

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

```vue [app/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/4.x/api/composables/use-async-data) 的第一个参数是一个唯一键，用于缓存第二个参数（查询函数）的响应。直接传入查询函数即可忽略此键，系统会自动生成键。
<br />

 <br />


由于自动生成的键只会考虑调用 `useAsyncData` 的位置，因此建议始终创建您自己的键，以避免不必要的行为，例如当您创建自己的自定义组合式函数来包装 `useAsyncData` 时。
<br />

 <br />


设置键有助于使用 [`useNuxtData`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-nuxt-data) 在组件间共享相同数据，或[刷新特定数据](https://nuxt.zhcndoc.com/docs/4.x/api/utils/refresh-nuxt-data#refresh-specific-data)。

</note>

```vue [app/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），因为这可能导致使用 nullish 值重复执行等意外行为。如果您需要触发副作用，请使用 [`callOnce`](https://nuxt.zhcndoc.com/docs/4.x/api/utils/call-once) 工具。

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

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

</note>

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-async-data">

详细了解 `useAsyncData`。

</read-more>

## 返回值

`useFetch` 和 `useAsyncData` 具有相同的返回值，列举如下。

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

<note>

`data`、`error` 和 `status` 是 Vue 的 refs，在 `<script setup>` 中可通过 `.value` 访问。

</note>

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

<note>

如果你没有在服务器端获取数据（例如使用 `server: false`），那么在 hydration 完成之前不会获取数据。这意味着即使你在客户端 `await useFetch`，在 `<script setup>` 中 `data` 仍然会保持为 undefined。

</note>

## 选项

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

### 延迟（Lazy）

默认情况下，数据获取的 composables 会在其异步函数解析完成之前使用 Vue 的 Suspense 等待，从而阻止导航。在客户端导航时，可以通过 `lazy` 选项忽略此功能。在这种情况下，您需要使用 `status` 值手动处理加载状态。

```vue [app/app.vue]twoslash
<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/4.x/api/composables/use-lazy-fetch) 和 `useLazyAsyncData` 作为便捷方法，实现相同的功能。

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

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-lazy-fetch">

详细了解 `useLazyFetch`。

</read-more>

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-lazy-async-data">

详细了解 `useLazyAsyncData`。

</read-more>

<video-accordion title="观看 Vue School 的视频，了解阻塞与非阻塞（lazy）请求" video-id="1022000555" platform="vimeo">



</video-accordion>

### 仅客户端获取

默认情况下，数据获取的 composables 会在客户端和服务器环境中执行它们的异步函数。将 `server` 选项设置为 `false` 可仅在客户端执行调用。在初次加载时，数据不会在 hydration 完成之前被获取，因此您必须处理挂起（pending）状态；不过在随后的客户端导航中，数据会在加载页面之前被等待。

与 `lazy` 选项结合使用时，这对首次渲染时不需要的数据（例如非 SEO 相关数据）非常有用。

```tstwoslash
/* 这个调用会在 hydration 之前执行 */
const articles = await useFetch('/api/article')

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

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

### 最小化 payload 大小

`pick` 选项可以帮助您最小化存储在 HTML 文档中的 payload 大小，只选择您希望从 composables 返回的字段。

```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` 都不会阻止初始不想要的数据被获取。但它们会防止不想要的数据被添加到从服务器转发到客户端的 payload 中。

</note>

<video-accordion title="观看 Vue School 的视频，了解如何最小化 payload 大小" video-id="1026410430" platform="vimeo">



</video-accordion>

### 缓存与重新获取

#### 键（Keys）

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

- [`useFetch`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-fetch) 会根据 URL、获取选项以及源代码中调用的位置生成键。这意味着，不同组件中两个 URL 相同的 `useFetch` 调用会使用**不同**的键，并各自发起请求。要在多个组件间共享相同的数据，请在作为最后一个参数传入的 `options` 对象中提供相同的显式 `key`。
- [`useAsyncData`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-async-data) 会在第一个参数为字符串时将其用作键。如果第一个参数是执行查询的 handler 函数，则会根据源代码中 `useAsyncData` 调用的位置为您生成唯一的键。

<tip>

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

</tip>

<video-accordion title="观看 Vue School 的视频，了解如何使用 key 选项缓存数据" video-id="1026410044" platform="vimeo">



</video-accordion>

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

当多个组件对相同键使用 `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

如果您想手动获取或刷新数据，请使用 composables 提供的 `execute` 或 `refresh` 函数。

```vuetwoslash
<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/4.x/getting-started/data-fetching#not-immediate)的情况下语义更明确。

<tip>

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

</tip>

#### 清除（Clear）

如果您想清除所提供的数据（出于任何原因），而不需要知道传递给 `clearNuxtData` 的特定键，可以使用 composables 提供的 `clear` 函数。

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

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

#### 监听（Watch）

要在应用中的其他响应式值发生变化时重新执行获取函数，请使用 `watch` 选项。您可以将其用于一个或多个可监听元素。

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

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

请注意，监听响应式值不会更改被获取的 URL。例如，下面的代码会一直获取初始用户 ID 对应的相同 URL，因为 URL 在函数被调用时就已构建。

```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/4.x/getting-started/data-fetching#computed-url)。

提供响应式获取选项时，它们会被自动监听并触发重新获取。在某些情况下，您可能希望通过指定 `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（Computed 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/4.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,
})
</script>

<template>
  <div>
    <!-- 在获取时禁用输入 -->
    <input
      v-model="id"
      type="number"
      :disabled="status === 'pending'"
    >

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

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

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

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

### 非立即执行（Not immediate）

`useFetch` composable 会在调用的瞬间开始获取数据。您可以通过设置 `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` 不会包含用户浏览器的 cookies，也不会将 fetch 响应中的 cookie 转发回客户端。

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

### 在 SSR 响应中将服务器端 API 调用的 Cookie 传回客户端

如果您想将 cookie 从内部请求传回/代理到客户端，您需要自行处理。

```ts [app/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">
// 该 composable 会自动将 cookies 传递到客户端
const event = useRequestEvent()

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

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

## Options 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 组件。

</note>

<read-more to="https://nuxt.zhcndoc.com/docs/4.x/api/utils/define-nuxt-component">



</read-more>

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

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

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

<note>

请注意，这*不适用于*通过 `$fetch` 或 `useFetch` 获取并从您的服务器路由传递的数据 - 更多信息请参见下一节。

</note>

## 从 API 路由序列化数据

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

<read-more to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description" icon="i-simple-icons-mdnwebdocs" target="_blank">

了解有关 `JSON.stringify` 限制的更多信息。

</read-more>

### 示例

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

```vue [app/app.vue]
<script setup lang="ts">
// 虽然我们返回了 Date 对象，但 `data` 类型推断为 string
const { data } = await useFetch('/api/foo')
</script>
```

### 自定义序列化函数

要自定义序列化行为，您可以在返回的对象上定义 `toJSON` 函数。如果您定义了 `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/app.vue]
<script setup lang="ts">
// `data` 类型被推断为:
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')
</script>
```

### 使用替代序列化器

Nuxt 当前不支持将序列化器替换为 `JSON.stringify` 之外的方案。不过，您可以将 payload 作为普通字符串返回，并利用 `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/app.vue]
<script setup lang="ts">
import superjson from 'superjson'

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

## 参考用例（Recipes）

### 通过 POST 请求使用 SSE（Server-Sent Events）

<tip>

如果您通过 GET 请求消费 SSE，可以使用 [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) 或 VueUse 的 composable [`useEventSource`](https://vueuse.org/core/useEventSource/)。

</tip>

当通过 POST 请求消费 SSE 时，您需要手动处理连接。如下所示：

```ts
// 向 SSE 端点发起 POST 请求
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: '你好，AI，你好吗？',
  },
  responseType: 'stream',
})

// 使用 TextDecoderStream 创建新的 ReadableStream，以文本形式获取数据
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 的视频，了解并行数据获取" video-id="1024262536" platform="vimeo">



</video-accordion>


## Sitemap

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