Skip to content

$fetch

$fetch 是 Nuxt 提供的 HTTP 请求工具,基于 ofetch。与 useFetch 不同,它是纯请求工具——不涉及响应式、SSR 数据传递、缓存等机制。

为什么需要 $fetch?useFetch 不够用吗?

场景useFetch$fetch
页面加载数据✅ 自动 SSR 传递❌ 客户端重复请求
按钮点击提交表单❌ 返回 ref 多余✅ 直接拿结果
事件处理❌ 不需要响应式✅ 轻量直接
服务端 API 内部调用❌ SSR 机制无意义✅ 适合纯请求
不需要缓存❌ 缓存反而占内存✅ 每次都是新请求

核心区别

useFetch数据管理器(负责获取 + 响应式 + 缓存 + SSR 传递),$fetch纯请求器(只负责发请求拿结果)。

简单记忆

  • 页面顶层加载数据 → useFetch
  • 事件处理、表单提交、服务端内部调用 → $fetch

基本用法

ts
// 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 的核心区别

特性$fetchuseFetch
返回值直接返回数据返回 ref 对象({ data, pending, error, ... }
SSR 数据传递❌ 不传递,客户端重复请求✅ 自动通过 Payload 传递
响应式❌ 非响应式✅ 返回 Ref,自动响应式
自动 key❌ 无 key 机制✅ 自动生成去重 key
去重❌ 每次都发请求✅ 相同 key 自动去重
适用场景事件处理、非页面请求页面数据加载

INFO

最常见的错误:在页面顶层用 $fetch 加载数据

ts
// ❌ 错误用法:SSR 时服务端获取了数据,但客户端不知道,会重复请求
const users = await $fetch('/api/users')
ts
// ✅ 正确用法:页面数据用 useFetch
const { data: users } = await useFetch('/api/users')

何时用 $fetch

  • 按钮点击等事件处理:提交表单、删除操作、点赞
  • 服务端 API 内部调用:在 server/api/ 中转发请求
  • 不需要响应式的场景:一次性获取、用完即弃
  • 表单提交onSubmit 中提交数据

何时用 useFetch

  • 页面初始化数据加载:列表页、详情页
  • 需要响应式数据:数据可能变化并触发 UI 更新
  • 需要 SSR 数据传递:避免客户端重复请求
  • 需要缓存:多组件共享同一份数据

请求选项

查询参数

ts
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,语义更清晰。

请求头

ts
const data = await $fetch('/api/me', {
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
})

基础 URL

ts
const data = await $fetch('/users', {
  baseURL: 'https://api.example.com',
})
// 实际请求:https://api.example.com/users

推荐用 createUseFetch 代替手动设置 baseURL

这样所有请求都自动带上。

超时

ts
const data = await $fetch('/api/slow', {
  timeout: 5000,  // 5 秒超时
})

INFO

超时行为:超时后抛出 FetchError 错误信息中包含 timeout 相关描述。务必用 try/catch 处理

请求体

ts
// 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

响应类型

ts
// 获取 Blob(文件下载)
const blob = await $fetch('/api/file', {
  responseType: 'blob',
})

// 获取 ArrayBuffer
const buffer = await $fetch('/api/binary', {
  responseType: 'arrayBuffer',
})

拦截器

$fetch 支持四个拦截器钩子,可以在请求/响应的不同阶段进行处理:

ts
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 抛错):

ts
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()自动解析

常见错误处理模式

ts
// 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):

ts
// 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

ts
// ❌ 服务端不能用相对路径调用外部 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、拦截器等):

ts
// 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')
      }
    },
  })
}

使用:

ts
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.createcreateUseFetch
返回纯请求函数useFetch 风格的组合式函数
SSR 数据传递
响应式
适用事件处理、表单提交页面数据加载

实际项目中推荐两者配合使用

  • createUseFetch → 页面数据加载
  • $fetch.create → 事件处理、表单提交

类型化请求

ts
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 导致数据重复请求

ts
// ❌ SSR 时服务端获取了数据,但客户端不知道,会再次请求
const data = await $fetch('/api/users')

// ✅ 页面数据用 useFetch,自动处理 SSR 数据传递
const { data } = await useFetch('/api/users')

2. 404 等状态码抛出错误

$fetch 在 4xx/5xx 状态码时会抛出错误,不同于 useFetch 返回 error ref:

ts
// 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

ts
// ❌ 服务端不能用相对路径调外部 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 会自动导入:

ts
// ❌ 不要这样做
import { useFetch } from '@vueuse/core'

// ✅ Nuxt 的 useFetch 会自动导入,无需手动 import
const { data } = await useFetch('/api/users')

知识脉络

text
useFetch → useAsyncData → 你在这里:$fetch

                              ├─→ 相关:懒加载获取($fetch 无 lazy 概念)

                              └─→ 相关:缓存与刷新($fetch 无缓存机制)

基于 Nuxt 4 官方文档整理编写