$fetch
$fetch 是 Nuxt 提供的 HTTP 请求工具,基于 ofetch。与 useFetch 不同,它是纯请求工具——不涉及响应式、SSR 数据传递、缓存等机制。
为什么需要 $fetch?useFetch 不够用吗?
| 场景 | useFetch | $fetch |
|---|---|---|
| 页面加载数据 | ✅ 自动 SSR 传递 | ❌ 客户端重复请求 |
| 按钮点击提交表单 | ❌ 返回 ref 多余 | ✅ 直接拿结果 |
| 事件处理 | ❌ 不需要响应式 | ✅ 轻量直接 |
| 服务端 API 内部调用 | ❌ SSR 机制无意义 | ✅ 适合纯请求 |
| 不需要缓存 | ❌ 缓存反而占内存 | ✅ 每次都是新请求 |
核心区别
useFetch 是数据管理器(负责获取 + 响应式 + 缓存 + SSR 传递),$fetch 是纯请求器(只负责发请求拿结果)。
简单记忆
- 页面顶层加载数据 →
useFetch - 事件处理、表单提交、服务端内部调用 →
$fetch
基本用法
// GET 请求
const users = await $fetch('/api/users')
// POST 请求
const user = await $fetch('/api/users', {
method: 'POST',
body: { name: 'Alice', email: 'alice@example.com' },
})
// PUT 请求
await $fetch('/api/users/1', {
method: 'PUT',
body: { name: 'Alice Updated' },
})
// DELETE 请求
await $fetch('/api/users/1', {
method: 'DELETE',
})$fetch 的命名
前缀 $ 表示这是 Nuxt 自动注入的便捷方法,和 $route、$router 类似。它基于 ofetch,支持 Node.js 和浏览器环境。
与 useFetch 的核心区别
| 特性 | $fetch | useFetch |
|---|---|---|
| 返回值 | 直接返回数据 | 返回 ref 对象({ data, pending, error, ... }) |
| SSR 数据传递 | ❌ 不传递,客户端重复请求 | ✅ 自动通过 Payload 传递 |
| 响应式 | ❌ 非响应式 | ✅ 返回 Ref,自动响应式 |
| 自动 key | ❌ 无 key 机制 | ✅ 自动生成去重 key |
| 去重 | ❌ 每次都发请求 | ✅ 相同 key 自动去重 |
| 适用场景 | 事件处理、非页面请求 | 页面数据加载 |
INFO
️ 最常见的错误:在页面顶层用 $fetch 加载数据
// ❌ 错误用法:SSR 时服务端获取了数据,但客户端不知道,会重复请求
const users = await $fetch('/api/users')// ✅ 正确用法:页面数据用 useFetch
const { data: users } = await useFetch('/api/users')何时用 $fetch
- 按钮点击等事件处理:提交表单、删除操作、点赞
- 服务端 API 内部调用:在
server/api/中转发请求 - 不需要响应式的场景:一次性获取、用完即弃
- 表单提交:
onSubmit中提交数据
何时用 useFetch
- 页面初始化数据加载:列表页、详情页
- 需要响应式数据:数据可能变化并触发 UI 更新
- 需要 SSR 数据传递:避免客户端重复请求
- 需要缓存:多组件共享同一份数据
请求选项
查询参数
const data = await $fetch('/api/search', {
query: { q: 'nuxt', page: 1 },
// 也可以用 params(query 的别名)
params: { q: 'nuxt' },
})query vs params
两者效果相同,都会拼接到 URL 上(如 /api/search?q=nuxt&page=1)。推荐统一用 query,语义更清晰。
请求头
const data = await $fetch('/api/me', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
})基础 URL
const data = await $fetch('/users', {
baseURL: 'https://api.example.com',
})
// 实际请求:https://api.example.com/users推荐用 createUseFetch 代替手动设置 baseURL
这样所有请求都自动带上。
超时
const data = await $fetch('/api/slow', {
timeout: 5000, // 5 秒超时
})INFO
️ 超时行为:超时后抛出 FetchError 错误信息中包含 timeout 相关描述。务必用 try/catch 处理
请求体
// JSON 请求体(自动设置 Content-Type)
const data = await $fetch('/api/users', {
method: 'POST',
body: { name: 'Alice' },
})
// FormData 请求体(自动设置 multipart/form-data)
const formData = new FormData()
formData.append('file', fileInput.files[0])
const data = await $fetch('/api/upload', {
method: 'POST',
body: formData,
})
// URLSearchParams 请求体(自动设置 application/x-www-form-urlencoded)
const params = new URLSearchParams()
params.append('username', 'alice')
const data = await $fetch('/api/login', {
method: 'POST',
body: params,
})body 的智能处理
- 传入普通对象 → 自动 JSON 序列化 + 设置
Content-Type: application/json - 传入
FormData→ 自动设置Content-Type: multipart/form-data - 传入
URLSearchParams→ 自动设置Content-Type: application/x-www-form-urlencoded - 传入
string→ 直接发送,需要手动设置Content-Type
响应类型
// 获取 Blob(文件下载)
const blob = await $fetch('/api/file', {
responseType: 'blob',
})
// 获取 ArrayBuffer
const buffer = await $fetch('/api/binary', {
responseType: 'arrayBuffer',
})拦截器
$fetch 支持四个拦截器钩子,可以在请求/响应的不同阶段进行处理:
const data = await $fetch('/api/data', {
onRequest({ request, options }) {
// 请求发出前
console.log('发送请求:', request)
options.headers.set('Authorization', `Bearer ${token}`)
},
onRequestError({ request, options, error }) {
// 请求发送失败(网络错误等)
console.error('请求发送失败:', error)
},
onResponse({ request, response, options }) {
// 收到响应(2xx 状态码)
console.log('响应状态:', response.status)
},
onResponseError({ request, response, options }) {
// 收到错误响应(4xx/5xx 状态码)
console.error('响应错误:', response.status)
},
})拦截器 vs createUseFetch
拦截器适合单个请求的定制。如果有多个请求需要相同的拦截逻辑(如统一添加鉴权头、统一处理 401),推荐用 createUseFetch 或 $fetch.create 创建自定义实例。
错误处理
$fetch 在收到 4xx/5xx 状态码时会自动抛出 FetchError,这一点与浏览器原生 fetch 不同(原生 fetch 不会对 4xx/5xx 抛错):
try {
const data = await $fetch('/api/users/999')
} catch (error) {
if (error instanceof FetchError) {
console.log(error.statusCode) // 404
console.log(error.statusMessage) // Not Found
console.log(error.data) // 响应体(服务端返回的错误详情)
console.log(error.url) // 请求的 URL
console.log(error.request) // Request 对象
}
}$fetch vs 原生 fetch 的错误处理
| 行为 | 原生 fetch | $fetch |
|---|---|---|
| 4xx/5xx 状态码 | 不抛错,需检查 response.ok | 自动抛出 FetchError |
| 网络错误 | 抛出 TypeError | 抛出 FetchError |
| JSON 解析 | 需手动 response.json() | 自动解析 |
常见错误处理模式
// 1. 静默处理——错误不影响页面
async function likePost(id: number) {
try {
await $fetch(`/api/posts/${id}/like`, { method: 'POST' })
} catch {
// 点赞失败不影响用户
}
}
// 2. 显示错误提示
async function submitForm() {
try {
await $fetch('/api/posts', {
method: 'POST',
body: formData,
})
showToast('提交成功!')
} catch (error) {
if (error instanceof FetchError) {
showToast(error.data?.message || '提交失败')
}
}
}
// 3. 条件处理——根据状态码分别处理
async function deleteUser(id: number) {
try {
await $fetch(`/api/users/${id}`, { method: 'DELETE' })
} catch (error) {
if (error instanceof FetchError) {
if (error.statusCode === 403) {
showToast('没有权限删除')
} else if (error.statusCode === 404) {
showToast('用户不存在')
} else {
showToast('删除失败')
}
}
}
}在服务端使用
在服务端 API 路由中,使用 event.$fetch 可以转发请求上下文(如 cookies、headers):
// server/api/forward.ts
export default defineEventHandler(async (event) => {
// ✅ event.$fetch 会自动转发请求头和 cookies
const data = await event.$fetch('/api/internal/data')
return data
})event.$fetch vs 普通 $fetch
event.$fetch:继承当前请求的上下文(cookies、headers),适合服务端内部转发- 普通
$fetch:不继承上下文,适合调用外部 API
INFO
️ 在服务端调用外部 API 时 需要提供完整 URL
// ❌ 服务端不能用相对路径调用外部 API
const data = await $fetch('/data', { baseURL: 'https://api.example.com' })
// ✅ 使用完整 URL
const data = await $fetch('https://api.example.com/data')全局配置——$fetch.create
创建自定义的 $fetch 实例,预设默认选项(baseURL、headers、拦截器等):
// app/composables/useApi.ts
export const useApi = () => {
const config = useRuntimeConfig()
const token = useCookie('auth-token')
return $fetch.create({
baseURL: config.public.apiBase,
headers: {
Authorization: token.value ? `Bearer ${token.value}` : '',
},
onRequestError({ error }) {
console.error('API 请求错误', error)
},
onResponseError({ response }) {
if (response.status === 401) {
navigateTo('/login')
}
},
})
}使用:
const api = useApi()
// GET 请求
const users = await api('/users')
// POST 请求
const user = await api('/users', {
method: 'POST',
body: { name: 'Alice' },
})$fetch.create vs createUseFetch
| 特性 | $fetch.create | createUseFetch |
|---|---|---|
| 返回 | 纯请求函数 | useFetch 风格的组合式函数 |
| SSR 数据传递 | ❌ | ✅ |
| 响应式 | ❌ | ✅ |
| 适用 | 事件处理、表单提交 | 页面数据加载 |
实际项目中推荐两者配合使用
createUseFetch→ 页面数据加载$fetch.create→ 事件处理、表单提交
类型化请求
interface User {
id: number
name: string
email: string
}
// 泛型指定响应类型
const users = await $fetch<User[]>('/api/users')
// users 类型为 User[]
const user = await $fetch<User>('/api/users/1')
// user 类型为 User
// 配合请求体类型
interface CreateUserBody {
name: string
email: string
}
const newUser = await $fetch<User, CreateUserBody>('/api/users', {
method: 'POST',
body: { name: 'Alice', email: 'alice@example.com' },
})类型推断
如果 API 路由使用了 defineEventHandler 并有正确的返回类型,$fetch 可以自动推断响应类型,不需要手动指定泛型。
常见问题
1. SSR 中用 $fetch 导致数据重复请求
// ❌ SSR 时服务端获取了数据,但客户端不知道,会再次请求
const data = await $fetch('/api/users')
// ✅ 页面数据用 useFetch,自动处理 SSR 数据传递
const { data } = await useFetch('/api/users')2. 404 等状态码抛出错误
$fetch 在 4xx/5xx 状态码时会抛出错误,不同于 useFetch 返回 error ref:
// useFetch:错误在 error ref 中
const { data, error } = await useFetch('/api/users/999')
if (error.value) {
// 处理错误
}
// $fetch:需要 try/catch
try {
const data = await $fetch('/api/users/999')
} catch (error) {
// 处理错误
}3. 服务端请求需要完整 URL
在服务端 API 中调用外部 API 时,需要提供完整 URL 或使用 baseURL:
// ❌ 服务端不能用相对路径调外部 API
const data = await $fetch('https://api.example.com/data')
// ✅ 使用 baseURL
const data = await $fetch('/data', {
baseURL: 'https://api.example.com',
})4. 与 VueUse 的 useFetch 冲突
确保不要手动导入 VueUse 的 useFetch,Nuxt 的 useFetch 会自动导入:
// ❌ 不要这样做
import { useFetch } from '@vueuse/core'
// ✅ Nuxt 的 useFetch 会自动导入,无需手动 import
const { data } = await useFetch('/api/users')知识脉络
useFetch → useAsyncData → 你在这里:$fetch
│
├─→ 相关:懒加载获取($fetch 无 lazy 概念)
│
└─→ 相关:缓存与刷新($fetch 无缓存机制)