在 Nuxt.js(特别是 Nuxt 3 )项目中,$fetch、useFetch 和 useAsyncData 是三种核心的数据获取方式。它们都基于 Nitro 引擎和 Vue 3 的组合式 API,但使用场景、功能封装和适用上下文有明显区别。
下面从 功能定位、使用场景、SSR 支持、返回值、错误处理、缓存机制 等维度详细对比,并给出清晰的选型建议。
📊 一、三者对比总览
| 特性 | $fetch |
useFetch(url, options?) |
useAsyncData(key, handler, options?) |
|---|---|---|---|
| 本质 | 封装的 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 应用。如需具体场景示例(如分页、搜索、认证请求)