---
title: "useApiFetch"
description: "基于 Nuxt useFetch 封装的 API 请求 composable，提供自动认证、业务状态码检查和 Toast 提示。"
seo_title: "useApiFetch"
seo_description: "SSR-friendly data fetching with multi-endpoint, auth, business code check and toast — the foundation of the Movk Nuxt API system."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/api/use-api-fetch"
---
# useApiFetch

> 基于 Nuxt useFetch 封装的 API 请求 composable，提供自动认证、业务状态码检查和 Toast 提示。

## 用法

使用自动导入的 `useApiFetch` composable 进行 API 请求，基于 Nuxt [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch){rel="[\"nofollow\"]"} 封装，提供自动认证、数据解包、业务状态码检查和统一的错误处理。

```vue
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

// 基础用法(自动解包 data 字段)
const { data, pending, error, refresh } = await useApiFetch<User[]>('/users')

// POST 请求
const { data } = await useApiFetch('/users', {
  method: 'POST',
  body: { name: 'test', email: 'test@example.com' }
})

// 使用其他端点
const { data } = await useApiFetch('/users', { endpoint: 'v2' })
</script>
```

- `useApiFetch` 继承 Nuxt [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch){rel="[\"nofollow\"]"} 的所有功能，同时提供额外的 API 集成特性。
- 自动从 session 获取 token 并添加到请求头(通过 `$api` 实例)。
- 自动检查业务状态码并抛出错误。
- 内置 Toast 提示，支持自定义配置。

## 数据请求模式

Movk Nuxt 提供三种数据请求 composable，签名一致，按执行时机选择：

| Composable                                            | 执行时机      | 阻塞导航 | 场景         |
| ----------------------------------------------------- | --------- | ---- | ---------- |
| [`useApiFetch`](/docs/api/use-api-fetch)              | SSR + CSR | 是    | 首屏核心数据     |
| [`useLazyApiFetch`](/docs/api/use-lazy-api-fetch)     | SSR + CSR | 否    | 次要 / 非首屏数据 |
| [`useClientApiFetch`](/docs/api/use-client-api-fetch) | 仅 CSR     | 否    | 非 SEO 敏感数据 |

`useApiFetch` 在 SSR 阶段就发起请求，首屏直出数据：

```vue [ApiQuickstartSsrExample.vue]
<script setup lang="ts">
interface Profile {
  id: string
  name: string
  email: string
  role: string
}

const { data, pending, error, refresh } = await useApiFetch<Profile>('/profile')
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="flex items-center gap-2 flex-wrap">
      <UBadge v-if="pending" color="warning" variant="subtle">
        pending
      </UBadge>
      <UBadge v-else-if="error" color="error" variant="subtle">
        error
      </UBadge>
      <UBadge v-else color="success" variant="subtle">
        ready
      </UBadge>
      <UButton size="sm" variant="outline" icon="i-lucide-refresh-cw" @click="refresh()">
        刷新
      </UButton>
    </div>
    <pre class="text-xs p-3 rounded bg-elevated overflow-auto">{{ error ? { error: error.message } : data }}</pre>
  </div>
</template>
```

## 数据解包与转换

`useApiFetch` 接收到的已是 [`$api`](/docs/api/api-plugin#%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%8C%85%E4%B8%8E%E4%B8%9A%E5%8A%A1%E6%A0%A1%E9%AA%8C) 解包后的业务数据，泛型 `T` 直接声明该数据类型；`transform` 接收解包后的数据做二次转换：

```ts
// 泛型 T 直接声明业务数据类型（即 data 字段的类型）
const { data } = await useApiFetch<User>('/user')
// data.value = { id: 1, name: 'test' }

// 结合 transform 做二次转换
const { data } = await useApiFetch<{ content: User[] }, SelectItem[]>('/users', {
  transform: ({ content }) => content.map(u => ({ label: u.name, value: u.id }))
})
```

> \[\!TIP\]
> See: /docs/api/api-plugin#数据解包与业务校验
> 
> 解包机制、
> 
> skipUnwrap
> 
>  / 
> 
> skipBusinessCheck
> 
>  的交互示例见 
> 
> $api
> 
>  页

## 业务状态码检查

`$api` 拦截器自动检查业务状态码，不在 `successCodes` 中时抛出 `ApiError`（见下方[错误分类](#%E9%94%99%E8%AF%AF%E5%88%86%E7%B1%BB)）。传 `skipBusinessCheck: true` 跳过校验（不抛 `ApiError`），但仍解包 `dataKey` 字段：

```ts
const { data } = await useApiFetch('/external', {
  skipBusinessCheck: true
})
```

> \[\!TIP\]
> See: /docs/api/api-plugin#数据解包与业务校验
> 
> 业务校验与 
> 
> skipBusinessCheck
> 
>  的交互示例见 
> 
> $api
> 
>  页

## 错误分类

模块只产出 `ApiError`（业务错误）一种自定义错误，其余均为 ofetch 原生 `FetchError`；通过 `isBusinessError` 与 `statusCode` 区分场景。下拉切换错误类型观察 `error.value` 形态：

- `business`：HTTP 200 但 `code` 不在 `successCodes` 内 → `ApiError`，`isBusinessError` 为 true。
- `http-400` / `http-500`：原生 `FetchError`，`statusCode` 为对应码。
- `http-422`：`FetchError` 的 `data` 字段携带服务端补充信息。
- `network`：连接被强制断开 → 无 `statusCode` 的 `FetchError`。

```vue [ApiErrorsExample.vue]
<script setup lang="ts">
import type { ApiError } from '@movk/nuxt'
import type { FetchError } from 'ofetch'

const props = defineProps<{
  mode: 'business' | 'http-400' | 'http-422' | 'http-500' | 'network'
}>()

const urlMap: Record<typeof props.mode, string> = {
  'business': '/demo/errors?type=business',
  'http-400': '/profile?fail=1',
  'http-422': '/demo/errors?type=422',
  'http-500': '/demo/errors?type=500',
  'network': '/demo/errors?type=network'
}

const { error, execute } = useApiFetch(() => urlMap[props.mode], {
  immediate: false,
  toast: false
})

const info = computed(() => {
  const err = error.value
  if (!err) return null
  const apiErr = err as Partial<ApiError>
  const fetchErr = err as FetchError
  return {
    kind: apiErr.isBusinessError ? 'ApiError（业务错误）' : 'FetchError',
    statusCode: apiErr.statusCode ?? fetchErr.statusCode ?? null,
    message: err.message,
    isBusinessError: apiErr.isBusinessError ?? false,
    data: fetchErr.data ?? null
  }
})
</script>

<template>
  <div class="flex flex-col gap-3">
    <UButton size="sm" color="error" variant="outline" icon="i-lucide-circle-alert" @click="execute()">
      触发 {{ mode }}
    </UButton>
    <p class="text-xs text-muted">
      模块只产出 <code>ApiError</code>（业务错误）一种自定义错误，其余均为 ofetch 原生 <code>FetchError</code>；通过 <code>isBusinessError</code> 与 <code>statusCode</code> 区分场景。
    </p>
    <pre class="text-xs p-3 rounded bg-elevated overflow-auto">{{ info }}</pre>
  </div>
</template>
```

## Toast 提示

`toast` 选项支持请求级配置：`false` 全关、`{ success: false }` 仅留错误、`successMessage` / `errorMessage` 快捷文本，或传完整 [`Toast`](https://ui.nuxt.com/components/toast){rel="[\"nofollow\"]"} 属性（`color`、`icon`、`duration` 等）：

```ts
// 关闭 Toast / 仅保留错误提示 / 快捷文本
await useApiFetch('/users', { toast: false })
await useApiFetch('/users', { toast: { success: false } })
await useApiFetch('/users', {
  toast: { successMessage: '创建成功!', errorMessage: '创建失败,请重试' }
})

// 完整 Toast 属性
await useApiFetch('/users', {
  toast: {
    success: { title: '创建成功', color: 'success', icon: 'i-lucide-circle-check' },
    error: { title: '创建失败', color: 'error', duration: 5000 }
  }
})

// 全局关闭成功提示时，单次请求用 show: true 开启
await useApiFetch('/users', { toast: { success: { show: true } } })
```

> \[\!NOTE\]
> 
> 开关优先级：请求级 
> 
> show
> 
>  显式声明 > 全局 
> 
> success.show
> 
>  / 
> 
> error.show
> 
>  \> 全局 
> 
> enabled
> 
> 。全局配置为 
> 
> toast.success.show: false
> 
>  时，请求级传 
> 
> { success: { show: true } }
> 
>  仍会弹出。

> \[\!TIP\]
> See: /docs/api/api-plugin#toast-提示
> 
> 五种 Toast 模式的交互示例见 
> 
> $api
> 
>  页

## API

### useApiFetch()

`useApiFetch<T = unknown, DataT = T>(url: string | (() => string), options?: UseApiFetchOptions<T, DataT>): UseApiFetchReturn<DataT>`

创建 API 请求。

#### Parameters

**url** (`string | (() => string)`) *required*: 请求 URL 或返回 URL 的函数。支持响应式 URL。

**options** (`UseApiFetchOptions<T, DataT>`): 请求配置选项。支持所有 Nuxt useFetch 选项，以及额外的 API 集成选项。API 集成选项使用指定的端点配置。默认使用 $api 的默认端点。Toast 提示配置。设为 false 禁用 Toast。成功提示配置。可以是完整的 Toast 属性对象，或 false 禁用成功提示；show: true 可覆盖全局关闭。错误提示配置。可以是完整的 Toast 属性对象，或 false 禁用错误提示；show: true 可覆盖全局关闭。成功提示的快捷文本。等价于 success: { title: '...' }。错误提示的快捷文本。等价于 error: { title: '...' }。跳过业务状态码检查（不抛出 ApiError），但仍会解包 dataKey 字段。默认 false。跳过数据解包。设为 true 时返回 code/message/data 完整信封。与 skipBusinessCheck 正交。默认 false。请求选项 (继承自 useFetch)HTTP 请求方法。默认 'GET'。URL 查询参数，会自动序列化到 URL。query 的别名。请求体。对于 POST/PUT/PATCH 请求，会自动序列化为 JSON。请求头。基础 URL。通常通过 endpoint 选项配置，无需手动设置。响应处理转换响应数据的函数。接收已解包的业务数据，返回最终数据。仅选择响应数据的指定字段。设置默认值的工厂函数。执行选项是否立即执行请求。设为 false 时需手动调用 execute()。默认 true。是否使用 lazy 模式。lazy 模式下不会阻塞客户端导航，数据在后台加载。默认 false。是否在服务端执行请求。默认 true。监听的响应式源，变化时自动重新请求。可以是 ref、reactive 对象或数组。是否深度监听 watch 的对象。默认 true。缓存与去重自定义缓存键。用于多个请求之间共享数据或去重。重复请求的处理策略。默认 cancel'cancel' - 取消待处理的请求'defer' - 不发起新请求从缓存中获取数据的函数。返回值将用作初始 data。请求钩子请求发送前的钩子。与内置钩子合并执行。onRequest({ request, options }) {
  console.log('发送请求:', request.url)
}
请求错误时的钩子。与内置钩子合并执行。onRequestError({ request, error }) {
  console.error('请求错误:', error)
}
收到响应时的钩子。内置钩子先执行，因此 response.\_data 已是解包后的业务数据（除非 skipUnwrap: true），且仅业务成功时触发。返回值被 ofetch 忽略，仅用于副作用；变形输出请用 transform。onResponse({ response }) {
  console.log('已解包数据:', response.\_data)
}
响应错误时的钩子(4xx/5xx)。与内置钩子合并执行。onResponseError({ response }) {
  console.error('响应错误:', response.status)
}

#### Type Parameters

**T** (`type`): 业务数据类型（已由 $api 自动解包）。即 API 响应中 data 字段的数据类型。

**DataT** (`type`): transform 转换后的最终类型。默认等于 T。

#### Returns

返回 [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch#return-values){rel="[\"nofollow\"]"} 的响应对象，包含以下属性:

**data** (`Ref<DataT | null>`): 响应数据(已解包和转换)。初始值为 null，请求成功后更新为实际数据。

**error** (`Ref<FetchError | ApiError | null>`): 错误对象。网络错误为 FetchError，业务状态码错误为 ApiError（包含 statusCode、response、isBusinessError 属性）。

**status** (`Ref<'idle' | 'pending' | 'success' | 'error'>`): 请求状态。'idle' - 尚未开始(仅 immediate: false 时)'pending' - 请求进行中'success' - 请求成功'error' - 请求失败

**pending** (`Ref<boolean>`): 是否正在请求中。等价于 status.value === 'pending'。

**refresh** (`(opts?: { dedupe?: boolean }) => Promise<void>`): 刷新数据，重新执行请求。别名: execute。// 刷新数据
await refresh()

// 跳过去重检查，强制刷新
await refresh({ dedupe: false })

**execute** (`(opts?: { dedupe?: boolean }) => Promise<void>`): refresh 的别名。手动执行请求(常用于 immediate: false 时)。

**clear** (`() => void`): 清空状态。将 data 设为 null、error 设为 null、status 设为 'idle'。

## Changelog

See commit history for [src/runtime/composables/useApiFetch.ts](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/composables/useApiFetch.ts).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
