数据获取之 useFetch
本教程共 50 篇 · 第 22 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:掌握 useFetch 的用法,能在组件里直接发起请求,并把返回的数据安全地渲染到页面上。
22-1
在网页里拿数据,最常用的办法就是发一个 HTTP 请求。Nuxt 自带一个请求工具叫 $fetch,它其实就是社区库 ofetch 的别名,全局自动导入,哪里都能直接用。
但问题来了:Nuxt 是「同构」框架,同一段代码既会在服务器上跑(用来生成 HTML),又会在浏览器里跑(用来让页面变「活」)。如果你在组件的 setup 里直接写 $fetch,那就会产生两次请求——服务器渲染时请求一次,浏览器「水合」时再请求一次。
<script setup lang="ts">
// ⚠️ 这样写会发两次请求,还可能引发水合不匹配
const data = await $fetch('/api/count')
</script>
请求两次不只是慢一点,更麻烦的是:服务器和浏览器拿到的结果可能不一样,于是 Vue 在「水合」阶段会发现两边对不上,抛出一个 hydration mismatch 警告。
为解决这个问题,Nuxt 提供了 useFetch 和 useAsyncData 这两个组合式函数(composable)。它们的核心本事是:如果请求在服务器上已经发过一次,那次的结果会被塞进一个叫 payload 的对象里,跟着 HTML 一起发到浏览器;浏览器水合时就直接复用,不再发第二次。
Notepayload 是一个 JavaScript 对象,可以用
useNuxtApp().payload访问。你在开发时打开 Nuxt DevTools,切到 Payload 标签页,就能看到这些被「顺手捎带」到前端的数据。
22-2
useFetch 可以看作是「包了一层 $fetch、又包了一层 useAsyncData」的便捷封装。它的第一参数通常是一个 URL。最简单的用法,一行就能拿到数据:
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>页面访问量:{{ count }}</p>
</template>
因为是「SSR 安全」的,你不用担心它重复请求。URL 既可以写相对路径(如 /api/count,指向你自己的 server 目录),也可以写完整的外部地址(如 https://api.example.com/list)。
22-3
useFetch 返回的是一个对象,里面装着好几个有用的东西。我们用解构把它们取出来:
<script setup lang="ts">
const { data, pending, error, status, refresh } = await useFetch('/api/users')
</script>
逐个解释:
data:请求拿到的数据本体。pending:布尔值,请求还没完成时它是true。它是status的便捷包装。error:如果请求失败,这里装着错误信息;成功时为null。status:一个字符串,描述当前状态,取值是'idle'(还没开始)、'pending'(进行中)、'success'(成功)、'error'(失败)。refresh(别名execute):一个函数,用来手动重新请求、刷新数据。clear:一个函数,把data清空回初始值(或options.default给的值)。
data、pending、error、status 都是 Vue 的响应式引用(ref),在 <script setup> 里要用 .value 访问;在模板里直接写 data 就行。
Tip文档示例里大多会写
await useFetch(...),但其实不await也行。await改变的是「后面的代码要不要等数据」以及「客户端导航时会不会阻塞页面」。不await的话,data会先是个默认值,等请求回来再变成真正的数据。
22-4
因为请求可能失败、也可能要等,真正能跑的代码必须同时照顾到「加载中」和「出错了」两种状态。status 正好派上用场:
<script setup lang="ts">
const { data: users, status, error } = await useFetch('/api/users')
</script>
<template>
<div v-if="status === 'pending'">努力加载中……</div>
<div v-else-if="error">出错了:{{ error.message }}</div>
<ul v-else>
<li v-for="u in users" :key="u.id">{{ u.name }}</li>
</ul>
</template>
这样写,用户至少知道页面在干什么,而不是对着一片空白干等。
Warning如果你把
server选项设成false(只在浏览器发请求),那在服务器渲染阶段是不会去拿数据的。即使你在客户端await了useFetch,在<script setup>里data仍然会是空的——必须等水合完成后才会真正发起请求。这种场景请务必配合加载态处理。
22-5
useFetch 的最后一个参数是一个选项对象,可以精细控制行为。挑几个最常用的讲:
lazy:不阻塞导航。 默认情况下,第一次进入页面时 useFetch 会「堵住」页面,等数据回来才显示。加 lazy: true 就不阻塞,但你要自己处理加载态。
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', { lazy: true })
</script>
server:只在客户端请求。 设 server: false,请求就只在浏览器里发生。适合那些对 SEO 不重要、晚一点拿也没关系的数据(比如评论区)。
pick / transform:给 payload 减肥。 pick 只挑你需要的字段,让跟着 HTML 传的数据更小;transform 可以对结果做映射再返回。
<script setup lang="ts">
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description'],
})
</script>
Note
pick和transform不会减少「服务器实际去拿」的数据量,但它们能减少「从服务器传到浏览器 payload 里」的数据量,对首屏传输是实打实的优化。
22-6
useFetch 适合「组件初次加载就要拿的数据」。如果你是在用户点了按钮、提交了表单之后才发请求,那就该用 $fetch——这种「基于交互」的请求本来就只发生在浏览器,不需要 payload 复用机制:
<script setup lang="ts">
async function addTodo () {
await $fetch('/api/todos', {
method: 'POST',
body: { title: '学习 Nuxt' },
})
}
</script>
22-7
在 Nuxt 3 里 useFetch 的用法和 Nuxt 4 完全一致,没有行为差异。唯一不同的是目录位置:Nuxt 4 的页面组件放在 app/pages/ 下,Nuxt 3 直接放在根目录 pages/。请求写法本身不用改。
22-8
useFetch 是你在 Nuxt 里拿数据最省心的入口:它自动处理服务器/浏览器双端、避免重复请求、还顺手把结果塞进 payload。记住返回值里的 data、status、error、refresh 四个常用成员,再配合 lazy、server 等选项,绝大多数「读数据」场景都能覆盖。下一章我们看它的兄弟 useAsyncData,当 useFetch 不够用时怎么用。
22-8 useFetch 的进阶用法
除了基本的 GET 请求,useFetch 还支持 POST、PUT、DELETE 等 HTTP 方法。通过 method 选项指定请求方法,通过 body 传递请求体。提交表单时,这种用法非常常见。
useFetch 还能配合 watch 选项实现响应式请求。当某个参数变化时,自动重新发起请求。比如搜索框输入关键词后,列表数据自动更新,不需要手动触发。这种声明式的数据获取方式比传统的命令式写法更简洁、更不容易出错。
错误处理方面,useFetch 返回的 error 响应式引用会捕获请求失败的信息。你可以在模板里根据 error.value 显示友好的错误提示,而不是让页面直接崩溃。结合 Nuxt 的错误处理机制,能构建出健壮的数据获取流程。
22-9 useFetch 与认证令牌
在实际项目里,很多 API 请求需要携带认证令牌(如 JWT)。在 Nuxt 里,推荐的做法是创建一个请求拦截器,自动在每个请求的头信息里加上令牌。这样可以避免在每个 useFetch 调用里重复写认证逻辑。
令牌通常存在 Cookie 里(服务端也能读取),而不是 localStorage(服务端访问不到)。Nuxt 的 useCookie 组合式函数在服务端和客户端都能正常工作,是存储认证令牌的好选择。配合 useFetch 的 headers 选项,可以轻松实现带认证的请求。