数据获取
🎯 引言
学会这篇文章,你能在 Nuxt 项目里优雅地请求接口数据。你会掌握 useFetch、useAsyncData 和 $fetch 的区别与使用场景,理解它们在服务端渲染时是怎么工作的,以及如何避免重复请求。
🧱 为什么 Nuxt 要专门讲数据获取
普通 Vue 项目通常在 onMounted 里用 fetch 或 Axios 请求数据,页面首次打开时往往是“先白屏,再加载”。
Nuxt 支持服务端渲染(SSR):数据可以在服务器上先请求好,和 HTML 一起返回给浏览器。这样首屏更快,对 SEO 也更友好。
为了兼顾 SSR 和 CSR,Nuxt 提供了三个核心工具:
| 工具 | 作用 | 常用场景 |
|---|---|---|
useFetch | 组合式函数,自动处理 SSR/CSR 请求 | 页面初始化需要的数据 |
useAsyncData | 更底层,配合 $fetch 使用 | 需要自定义请求逻辑或组合多个请求 |
$fetch | 实际发起 HTTP 请求 | 在事件回调中请求数据 |
🚀 useFetch:最常用的数据获取方式
useFetch 是 Nuxt 提供的一个组合式函数,会自动在服务端请求数据,并把结果传给客户端,避免重复请求。
<template>
<div>
<h1>文章列表</h1>
<ul>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
<script setup>
const { data: posts } = await useFetch('https://jsonplaceholder.typicode.com/posts');
</script>
useFetch 返回的 data 是一个 ref,模板里可以直接用。await 表示等待数据返回后再渲染页面。⚡ useAsyncData + $fetch:更灵活的组合
如果你的请求逻辑比较复杂,比如要组合多个接口、或者在请求前处理参数,可以用 useAsyncData + $fetch。
<!-- app/pages/posts/[id].vue -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
</div>
</template>
<script setup>
const route = useRoute();
const { data: post } = await useAsyncData('post-detail', () => {
return $fetch(`https://jsonplaceholder.typicode.com/posts/${route.params.id}`);
});
</script>
useAsyncData 第一个参数是唯一 key,用来缓存和去重。同一个页面里不同请求不要用重复的 key。🆚 useFetch 和 useAsyncData 怎么选
| 场景 | 推荐方式 |
|---|---|
| 简单 GET 请求,直接拿接口数据 | useFetch |
| 需要组合多个请求、处理复杂逻辑 | useAsyncData + $fetch |
| 按钮点击、表单提交后再请求 | $fetch |
useFetch 是“一站式”,useAsyncData 是“自定义”,$fetch 是“裸请求”。🖱 事件触发请求用 $fetch
如果请求不是页面初始化就要的,而是用户点击按钮后才发起,直接用 $fetch 就行,不需要 useFetch。
<template>
<div>
<input v-model="keyword" placeholder="输入关键词" />
<button @click="handleSearch">搜索</button>
<ul>
<li v-for="item in results" :key="item.id">{{ item.title }}</li>
</ul>
</div>
</template>
<script setup>
const keyword = ref('');
const results = ref([]);
async function handleSearch() {
results.value = await $fetch(`https://api.example.com/search?q=${keyword.value}`);
}
</script>
useFetch,因为它只能在 setup 顶层或异步上下文里调用一次,不适合重复触发。🪤 SSR 下的重复请求问题
为什么推荐用 useFetch 而不是直接在 onMounted 里请求?
因为 useFetch 会在服务端先请求一次,把数据注入到 HTML 里;客户端激活时,Nuxt 会复用这份数据,不会再次请求。
如果你在 onMounted 里用 fetch,服务端不会执行,客户端加载完页面后再请求一次,首屏就是白屏,也失去了 SSR 的意义。
🛠 常用参数
lazy:延迟加载
默认 useFetch 会阻塞页面渲染,直到数据返回。如果不希望阻塞,可以加 lazy: true:
const { data: posts, pending } = await useFetch('/api/posts', {
lazy: true,
});
配合 pending 可以显示加载状态:
<template>
<div>
<p v-if="pending">加载中...</p>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
🧾 小节总结
- Nuxt 的数据获取工具要考虑 SSR,
useFetch是最常用的选择。 useFetch适合简单请求,会自动处理服务端和客户端的数据复用。useAsyncData+$fetch适合复杂逻辑或多请求组合。$fetch适合事件触发后的请求,不要在onMounted里做首屏数据请求。lazy: true可以让请求不阻塞页面渲染,配合pending显示加载状态。
❓ 知识问答
Q1:为什么首屏数据请求推荐 useFetch 而不是在 onMounted 里请求?
onMounted 只在客户端执行,服务端不会请求数据,首屏会白屏,也失去了 SSR 的优势。
Q2:useFetch 和 $fetch 有什么关系?
useFetch 内部会调用 $fetch 真正发起请求,但它额外处理了 SSR 数据注入、客户端复用、key 缓存等逻辑。
Q3:useAsyncData 的 key 可以省略吗?
可以省略,Nuxt 会根据请求地址自动生成 key。但如果请求逻辑复杂或地址相同参数不同,建议手动写 key 避免冲突。
Q4:lazy: true 和默认行为有什么区别?
默认行为会等待数据返回再渲染页面;lazy: true 会立即渲染页面,数据回来后再更新,适合不重要或次要的数据。
🧪 小练习
完成一个文章详情页:
- 创建
app/pages/posts/[id].vue。 - 使用
useFetch请求https://jsonplaceholder.typicode.com/posts/${id}。 - 显示文章标题和正文。
- 添加返回
/posts的链接(可以先创建app/pages/posts/index.vue作为列表页)。
<!-- app/pages/posts/[id].vue -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
<NuxtLink to="/posts">返回列表</NuxtLink>
</div>
</template>
<script setup>
const route = useRoute();
// 请在这里使用 useFetch 获取文章详情
const { data: post } = await useFetch(
// 请在这里拼接接口地址
);
</script>
🎉 恭喜你已经掌握了 Nuxt 的数据获取!下一篇我们会学习 Nuxt 中的状态管理,理解 useState 和 Pinia 在项目中的分工。
