Nuxt.js

数据获取

掌握 Nuxt 中 useFetch、useAsyncData 和 $fetch 的用法,理解服务端渲染下的数据获取机制。

🎯 引言

学会这篇文章,你能在 Nuxt 项目里优雅地请求接口数据。你会掌握 useFetchuseAsyncData$fetch 的区别与使用场景,理解它们在服务端渲染时是怎么工作的,以及如何避免重复请求。


🧱 为什么 Nuxt 要专门讲数据获取

普通 Vue 项目通常在 onMounted 里用 fetch 或 Axios 请求数据,页面首次打开时往往是“先白屏,再加载”。

Nuxt 支持服务端渲染(SSR):数据可以在服务器上先请求好,和 HTML 一起返回给浏览器。这样首屏更快,对 SEO 也更友好。

为了兼顾 SSR 和 CSR,Nuxt 提供了三个核心工具:

工具作用常用场景
useFetch组合式函数,自动处理 SSR/CSR 请求页面初始化需要的数据
useAsyncData更底层,配合 $fetch 使用需要自定义请求逻辑或组合多个请求
$fetch实际发起 HTTP 请求在事件回调中请求数据

🚀 useFetch:最常用的数据获取方式

useFetch 是 Nuxt 提供的一个组合式函数,会自动在服务端请求数据,并把结果传给客户端,避免重复请求。

app/pages/posts/index.vue
<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

app/pages/search.vue
<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 会立即渲染页面,数据回来后再更新,适合不重要或次要的数据。


🧪 小练习

完成一个文章详情页:

  1. 创建 app/pages/posts/[id].vue
  2. 使用 useFetch 请求 https://jsonplaceholder.typicode.com/posts/${id}
  3. 显示文章标题和正文。
  4. 添加返回 /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 在项目中的分工。