useApiFetch
用法
使用自动导入的 useApiFetch composable 进行 API 请求,基于 Nuxt useFetch 封装,提供自动认证、数据解包、业务状态码检查和统一的错误处理。
<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继承 NuxtuseFetch的所有功能,同时提供额外的 API 集成特性。- 自动从 session 获取 token 并添加到请求头(通过
$api实例)。 - 自动检查业务状态码并抛出错误。
- 内置 Toast 提示,支持自定义配置。
数据请求模式
Movk Nuxt 提供三种数据请求 composable,签名一致,按执行时机选择:
| Composable | 执行时机 | 阻塞导航 | 场景 |
|---|---|---|---|
useApiFetch | SSR + CSR | 是 | 首屏核心数据 |
useLazyApiFetch | SSR + CSR | 否 | 次要 / 非首屏数据 |
useClientApiFetch | 仅 CSR | 否 | 非 SEO 敏感数据 |
useApiFetch 在 SSR 阶段就发起请求,首屏直出数据:
{
"id": "me",
"name": "Movk Demo",
"email": "demo@movk.dev",
"avatar": "/avatar.png",
"role": "developer",
"issuedAt": "2026-06-21T11:00:41.697Z"
}<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 解包后的业务数据,泛型 T 直接声明该数据类型;transform 接收解包后的数据做二次转换:
// 泛型 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 }))
})
业务状态码检查
$api 拦截器自动检查业务状态码,不在 successCodes 中时抛出 ApiError(见下方错误分类)。传 skipBusinessCheck: true 跳过校验(不抛 ApiError),但仍解包 dataKey 字段:
const { data } = await useApiFetch('/external', {
skipBusinessCheck: true
})
错误分类
模块只产出 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。
模块只产出 ApiError(业务错误)一种自定义错误,其余均为 ofetch 原生 FetchError;通过 isBusinessError 与 statusCode 区分场景。
<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 属性(color、icon、duration 等):
// 关闭 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 }
}
})
API
useApiFetch()
useApiFetch<T = unknown, DataT = T>(url: string | (() => string), options?: UseApiFetchOptions<T, DataT>): UseApiFetchReturn<DataT>
创建 API 请求。
Parameters
useFetch 选项,以及额外的 API 集成选项。API 集成选项
$api 的默认端点。false 禁用 Toast。false 禁用成功提示。false 禁用错误提示。success: { title: '...' }。error: { title: '...' }。ApiError),但仍会解包 dataKey 字段。默认 false。true 时返回 code/message/data 完整信封。与 skipBusinessCheck 正交。默认 false。请求选项 (继承自 useFetch)
'GET'。query 的别名。endpoint 选项配置,无需手动设置。响应处理
执行选项
false 时需手动调用 execute()。默认 true。false。true。watch 的对象。默认 true。缓存与去重
cancel'cancel'- 取消待处理的请求'defer'- 不发起新请求
data。请求钩子
onRequest({ request, options }) {
console.log('发送请求:', request.url)
}
onRequestError({ request, error }) {
console.error('请求错误:', error)
}
onResponse({ response }) {
console.log('收到响应:', response.status)
}
onResponseError({ response }) {
console.error('响应错误:', response.status)
}
Type Parameters
$api 自动解包)。即 API 响应中 data 字段的数据类型。transform 转换后的最终类型。默认等于 T。Returns
返回 useFetch 的响应对象,包含以下属性:
null,请求成功后更新为实际数据。FetchError,业务状态码错误为 ApiError(包含 statusCode、response、isBusinessError 属性)。'idle'- 尚未开始(仅immediate: false时)'pending'- 请求进行中'success'- 请求成功'error'- 请求失败
status.value === 'pending'。execute。// 刷新数据
await refresh()
// 跳过去重检查,强制刷新
await refresh({ dedupe: false })
refresh 的别名。手动执行请求(常用于 immediate: false 时)。data 设为 null、error 设为 null、status 设为 'idle'。