在 Nuxt.js(特别是 Nuxt 3)项目中,$fetch、useFetch 和 useAsyncData 是三种核心的数据获取方式。它们都基于 Nitro 引擎和 Vue 3 的组合式 API,但使用场景、功能封装和适用上下文有明显区别。
下面从 功能定位、使用场景、SSR 支持、返回值、错误处理、缓存机制 等维度详细对比,并给出清晰的选型建议。
📊 一、三者对比总览
| 本质 | 封装的 fetch 工具函数 | useAsyncData + $fetch 的语法糖 | 通用异步数据加载组合函数 |
| 是否自动处理 loading/error | ❌ 否 | ✅ 是 | ✅ 是 |
| 是否支持 SSR 预取 | ⚠️ 需手动控制 | ✅ 自动(在 setup 中调用) | ✅ 自动 |
| 是否自动缓存/去重 | ❌ 否 | ✅ 按 URL 缓存(同 key) | ✅ 按 key 缓存 |
| 适用场景 | 手动请求、非初始数据、POST/PUT 等 | 获取远程 JSON 数据(GET 为主) | 任意异步逻辑(API、计算、DB 查询等) |
| 返回值 | Promise<T> | { data, pending, error, refresh } | { data, pending, error, refresh } |
| 是否需 await | ✅ 必须 | ✅ 推荐(或用 .then) | ✅ 推荐 |
🔍 二、详细说明与使用场景
1. $fetch:底层 HTTP 客户端(灵活但无状态管理)
- 用途:发起任意 HTTP 请求(GET/POST/PUT/DELETE),类似 fetch,但更智能。
- 特点:
- 自动解析 JSON
- 支持 TypeScript 类型推断
- 可在服务端或客户端运行
- 不提供 pending/error** 状态**,需自行处理
✅ 适用场景:
- 表单提交(POST)
- 用户交互触发的请求(如点赞、删除)
- 在 server/ 端点内部调用第三方 API
- 不需要自动 SSR 预加载的动态请求
示例:
// 在组件中(客户端交互)
const submitForm = async () => {
try {
const res = await $fetch('/api/create', {
method: 'POST',
body: { name: 'John' }
})
console.log(res)
} catch (error) {
console.error('提交失败', error)
}
}
💡 在 server/api/xxx.ts 中也常用 $fetch 调用真实后端。
2. useFetch(url, options?):专为远程数据获取设计
- 本质:useAsyncData(url, () => $fetch(url, options)) 的简写
- 自动按 URL 作为缓存 key
- 自动在 SSR 阶段执行(如果在 setup() 中调用)
✅ 适用场景:
- 页面初始化时加载列表、详情等 GET 数据
- 调用 /api/xxx 或外部 REST API(公开数据)
- 希望自动获得 data/pending/error 状态
示例:
<script setup>
// 自动 SSR + 客户端 hydration
const { data: posts, pending, error } = await useFetch('/api/posts')
// 带参数
const { data: user } = await useFetch(`/api/users/${userId}`)
</script>
<template>
<div v-if="pending">加载中…</div>
<div v-else-if="error">出错了:{{ error.message }}</div>
<div v-else>
<h1>{{ posts?.length }} 篇文章</h1>
</div>
</template>
✅ 推荐用于 页面级数据预取
3. useAsyncData(key, handler, options?):最通用的异步数据加载器
- 用途:执行任意异步函数(不限于 HTTP 请求)
- **必须提供唯一 **key(用于缓存和去重)
- handler 函数可以是 $fetch、数据库查询、复杂计算等
✅ 适用场景:
- 需要自定义异步逻辑(如合并多个 API)
- 使用 GraphQL、WebSocket 初始化
- 调用本地 Composable 或服务层方法
- 需要精确控制缓存 key
示例:
// 合并两个 API
const { data: combinedData } = await useAsyncData('user-posts', async () => {
const [user, posts] = await Promise.all([
$fetch('/api/user'),
$fetch('/api/posts')
])
return { user, posts }
})
// 调用 composables
const { data: profile } = await useAsyncData('profile', () => fetchUserProfile())
💡 当你需要 **超越简单 **$fetch 的逻辑时,用 useAsyncData
🧠 三、如何选择?—— 决策树
需要加载页面初始数据?
├─ 是 → 数据来自一个 URL(GET)?
│ ├─ 是 → 用 useFetch(url)
│ └─ 否 → 用 useAsyncData(key, handler)
└─ 否(用户交互、提交表单等)
└─ 用 $fetch()
⚠️ 四、注意事项
1. SSR 与客户端执行时机
- useFetch / useAsyncData 必须在 setup() 顶层 await 才能触发 SSR。
- 如果在 onMounted 中调用,只在客户端执行(失去 SSR 优势)。
✅ 正确(SSR):
<script setup>
const { data } = await useFetch('/api/data') // ✅ SSR
</script>
❌ 错误(仅 CSR):
<script setup>
onMounted(async () => {
const { data } = await useFetch('/api/data') // ❌ 仅客户端
})
</script>
2. 缓存与重复请求
- 相同 key(useAsyncData)或相同 url(useFetch)在一次请求周期内不会重复执行。
- 可通过 refresh() 手动重新获取。
3. 错误处理
- error 是一个 ref 对象,需用 .value 访问(但在模板中自动 unwrap)
- 全局错误可通过 app.vue 的 onErrorCaptured 或 Nuxt 插件统一处理
✅ 五、总结建议
| 页面加载用户列表、文章详情等 GET 数据 | useFetch('/api/xxx') |
| 需要合并多个 API 或自定义异步逻辑 | useAsyncData('my-key', () => {…}) |
| 提交表单、删除操作、按钮点击等交互 | $fetch('/api/action', { method: 'POST', body }) |
| 在 server/ 端点中调用第三方 API | $fetch(externalUrl) |
通过合理选择这三种方式,你可以构建出高性能、SEO 友好、用户体验佳的 Nuxt 应用。如需具体场景示例(如分页、搜索、认证请求)
