nuxt数据获取
数据获取(Data Fetching)是最核心的一环。如果你用传统的axios放在onMounted 里请求,爬虫抓到的依然是一个空壳,SEO 照样没戏。 Nuxt 提供了四个核心工具:useFet

nuxt数据获取

发布时间:2026-04-21 (51分钟前)

**数据获取(Data Fetching)**是最核心的一环。如果你用传统的axios放在onMounted 里请求,爬虫抓到的依然是一个空壳,SEO 照样没戏。

Nuxt 提供了四个核心工具:useFetchuseAsyncData$fetchuseLazyFetch

useFetch

它是 Nuxt 获取数据最简单的方式,会自动处理服务端渲染序列化(即:在服务器请求一次,把结果塞进 HTML,客户端直接复用,不会在浏览器里重复请求)。

<script setup lang="ts">
interface BaseResponse<T> {
  code: number
  data: T
  msg: string
}

interface ArticleType {
  title: string
  content: string
}

const {data, pending, error} = await useFetch<BaseResponse<ArticleType>>("http://127.0.0.1:4523/m1/8122291-7879433-default/api/article/detail")

</script>

<template>
  <div v-if="pending">加载中...</div>
  <div v-else-if="error">出错了:{{ error.message }}</div>
  <div v-else>
    <h1>{{ data.data.title }}</h1>
    <p>{{ data.data.content }}</p>
  </div>
</template>

<style scoped>

</style>

配置baseurl

直接写绝对路径不太容易维护,我们可以只写相对路径,通过代理去请求url

const {data, pending, error} = await useFetch<BaseResponse<ArticleType>>("/api/article/detail")

然后在nuxt.config.ts中配置nitro

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  devtools: { enabled: true },
  nitro: {
    devProxy: {
      // 当你请求 /api 时,Nitro 会自动转发到你的 Mock 地址,只在csr下生效
      "/api": {
        target: "http://127.0.0.1:4523/m1/8122291-7879433-default/api",
        changeOrigin: true,
      }
    },
    // 如果是线上环境,使用 routeRules 进行转发 (SSR 模式下非常强),ssr和csr都生效 
    routeRules: {
      '/api/**': { proxy: 'http://127.0.0.1:4523/m1/8122291-7879433-default/api/**' }
    }
  }
})

带参数

const { data } = await useFetch('/api/article/update', {
  method: 'POST',
  query: { id: 100 }, // URL 参数
  body: {
    title: '更新后的标题' // Body 参数
  }
})

pick

很多时候后端接口返回的对象非常大(比如包含create_time,update_time,author_email等),但你前端只需要titlecontent

const { data } = await useFetch('/api/article', {
pick: ['title', 'content', 'id'] // 只保留这三个字段进入 HTML
})

pick只能选第一层

transform数据转换

对应大部分的api数据都是嵌套结构,code data msg这样的,pick只能选第一层,所以我们得用transform处理数据

const { data } = await useFetch('/api/article', {
  transform: (res) => ({
    title: res.data.title,
    content: res.data.content
  })
})
// 此时 HTML 里的 __NUXT_DATA__ 只会存:{ title: "...", content: "..."

refresh函数

  • 知识点:useFetch的第一个参数其实是它的key
  • **常见坑:**如果你在同一个页面多次调用同一个接口,由于key 相同,数据可能不会更新。
const { data, refresh } = await useFetch('/api/article')
// 当点击“刷新”按钮时调用 refresh()

useLazyFetch

useLazyFetchuseFetch的一个变体,它的核心逻辑只有一句话:“先跳页面,再加载数据。”

通常情况下,Nuxt 的useFetch会阻塞路由跳转。也就是说,如果你在setup里用了await useFetch,用户点击链接后,浏览器会“卡”在当前页,直到新页面的数据请求完成后才执行跳转。

useLazyFetch 则打破了这种阻塞。

useLazyFetch等价于useFetch(url, { lazy: true })

它的行为特征如下:

  • 非阻塞跳转: 路由会立即切换,组件会立即挂载。
  • 前台异步: 数据在后台抓取,抓取过程中页面已经展示出来了。
  • **响应式:**它返回的pending属性会从true变为false,你可以据此展示加载动画(Loading)。

如果你有一个页面数据量很大(比如复杂的报表),接口可能需要 2 秒才能返回。

  • useFetch****: 用户点击后,页面卡住 2 秒没反应,用户以为点错了。
  • useLazyFetch****: 页面秒开,用户看到一个转圈圈或骨架屏,心理预期更舒服。
<script setup>
const { pending, data: posts } = useLazyFetch('/api/posts')
</script>

<template>
  <div v-if="pending">
    加载中... (或者显示骨架屏)
  </div>
  
  <div v-else>
    <div v-for="post in posts" :key="post.id">{{ post.title }}</div>
  </div>
</template>

注意:

使用useLazyFetch时,你必须处理**“数据不存在”** 的状态。

  1. **处理空数据:**因为组件是立即渲染的,此时data.valuenull。如果你在模板里直接访问data.user.name而不加v-if或可选链(?.),页面会直接报错挂掉。
  2. **Watch 监听:**如果你需要根据获取到的数据做进一步处理,通常需要配合watch来监听data的变化,因为在setup 执行的那一刻,数据还没回来。
const { data } = useLazyFetch('/api/user')

// ❌ 这样做拿不到数据
console.log(data.value) 

// ✅ 这样做才能在数据回来时处理
watch(data, (newData) => {
  if (newData) {
    console.log('数据到货了:', newData)
  }
})

总结:该选哪一个?

需求 推荐工具
SEO 至上,数据必须在首屏渲染出来 useFetch
追求极致跳转速度,允许先白屏/显示 Loading useLazyFetch
数据请求极快(< 100ms) useFetch (用户无感知)
数据请求很慢(> 500ms) useLazyFetch (避免页面假死)

如果你需要这个请求在csr阶段,可以加一个server:false

const {data, pending, status, error, refresh} =  useLazyFetch<userType>("/api/user/await", {
   server: false,
})

useAsyncData

useAsyncData是一个底层的数据获取工具。它不限制你获取数据的方式,你可以用它包裹任何返回 Promise 的逻辑(比如数据库查询、调用第三方 SDK 或甚至是定时器)。

语法结构

const { data, pending, error, refresh } = await useAsyncData(
  'key-unique', // 唯一的键,用于确保跨请求的数据缓存
  () => $fetch('https://api.example.com/data') // 异步逻辑
)

一般用它处理复杂的聚合逻辑

如果你需要在一个 Hook 里完成多个操作,比如:

  1. 先从 API A 获取 ID。
  2. 再用 ID 去 API B 获取详情。
  3. 最后返回合并后的结果。
const { data } = await useAsyncData('composite-data', async () => {
  const user = await $fetch('/api/user')
  const posts = await $fetch(`/api/posts/${user.id}`)
  return { user, posts }
})

核心差异对比

特性 useFetch useAsyncData
本质 专为 HTTP 请求设计。 通用的异步包装器。
便利性 。自动生成 key,直接传入 URL 即可。 。需要手动提供 key(除非你不在乎缓存)。
灵活度 。只能处理基于 URL 的请求。 。可以执行任何 Promise 逻辑。
使用场景 90% 的标准 API 调用。 复杂逻辑、多个请求合并、调用外部 SDK(如 Firebase)。

$fetch

简单来说,只要这个请求不是为了在页面一打开时就把内容渲染出来,而是为了响应某个动作,那就该$fetch 上场了。

用法类似axios,我们可以把它的使用场景归纳为**“非页面初始化”**的所有情况。

注意:不要在setup顶层直接写$fetch。这会导致 SSR 和客户端各跑一次请求!

  1. 提交数据(POST / PUT / DELETE)

这是$fetch 最经典的操作。比如用户注册、发表评论或删除订单。这些操作不需要 SSR(服务器渲染),只需要在点击时执行一次。

const submitForm = async () => {
  const response = await $fetch('/api/register', {
    method: 'POST',
    body: { username: 'Gemini', password: '123' }
  })
  // 处理注册成功逻辑
}
  1. 在中间件(Middleware)或插件(Plugins)中

如果你需要在路由跳转时验证权限,或者在应用启动时初始化一些数据,由于这些地方不在 Vue 组件的setup生命周期内,useFetch无法工作,必须使用$fetch

// middleware/auth.js
export default defineNuxtRouteMiddleware(async (to, from) => {
  const user = await $fetch('/api/check-auth')
  if (!user.isLoggedIn) {
    return navigateTo('/login')
  }
})
  1. Server API 内部的相互调用

在 Nuxt 的 server/api 目录下编写后端接口时,如果你想在一个接口里调用另一个接口,你也应该使用 $fetch(Nuxt 会智能地进行内部调用,而不产生实际的 HTTP 开销)。

文件上传和下载

$fetch是基于ofetch构建的,它非常智能。处理文件上传时,它会自动识别FormData并设置正确的Content-Type;处理下载时,则需要结合二进制流处理。

文件上传

<script setup lang="ts">
const uploadAvatar = async (event: Event) => {
  const el = event.target as HTMLInputElement
  const file = el.files?.[0]
  if (!file) return

  // 1. 构建 FormData
  const formData = new FormData()
  formData.append('file', file)
  formData.append('type', 'avatar') // 还可以附带其他字段

  try {
    // 2. 使用 $fetch 发送
    // 注意:不要手动设置 Content-Type,浏览器会自动处理并加上 boundary
    const res = await $fetch('/api/upload', {
      method: 'POST',
      body: formData,
      headers: { Authorization: `Bearer ${token}` }, // 如果你的上传接口需要鉴权,别忘了加 header
      // 如果需要监听进度(仅限客户端)
      onResponse({ response }) {
        console.log('上传成功')
      }
    })
    alert('上传成功!')
  } catch (err) {
    console.error('上传失败', err)
  }
}
</script>

<template>
  <input type="file" @change="uploadAvatar" />
</template>

文件下载

如果后端接口直接吐出文件二进制流,你需要将响应类型设为blob

const downloadInvoice = async (id: string) => {
  try {
    // 1. 请求时指定 responseType 为 'blob'
    const response = await $fetch<Blob>(`/api/download/${id}`, {
      responseType: 'blob'
    })

    // 2. 创建一个临时的 URL 对象
    const url = window.URL.createObjectURL(response)
    const link = document.createElement('a')
    link.href = url
    
    // 3. 设置文件名
    link.setAttribute('download', `invoice-${id}.pdf`)
    document.body.appendChild(link)
    
    // 4. 模拟点击并移除
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(url) // 释放内存
  } catch (err) {
    alert('下载失败')
  }
}

获取原始响应

如果你需要获取原始响应的数据

比如判断的http状态码,http响应头,有2种方法

  1. onResponse属性
  2. $fe
<script setup lang="ts">

const {data} = await useFetch("/api/user", {
  onResponse: (res) => {
    const response = res.response
    console.log(response.status, response._data, response.headers)
  }
})

function getResponse(){
  $fetch("/api/user", {
    onResponse: (res) => {
      const response = res.response
      console.log(response.status, response._data, response.headers)
    }
  })
}

async function getResponse1(){
  const response = await $fetch.raw("/api/user")
  console.log(response.status, response._data, response.headers)
}
</script>

<template>
  <div>原始响应
    <button @click="getResponse">获取响应</button>
    <button @click="getResponse1">获取响应1</button>
  </div>
</template>

<style scoped>

</style>