数据获取
Nuxt 给了 4 个数据获取工具:useFetch、useAsyncData、useLazyFetch、$fetch。它们的共同目标是 —— 在服务端获取、客户端复用、按 key 去重 —— 但各自适用的场景不同。
useFetch —— 90% 的场景
<script setup lang="ts">
const { data, pending, error, refresh, status } = await useFetch('/api/posts')
</script>
<template>
<p v-if="pending">加载中…</p>
<p v-else-if="error">出错了</p>
<ul v-else>
<li v-for="p in data" :key="p.id">{{ p.title }}</li>
</ul>
</template>
它自动做了:
- SSR 阶段在服务端请求,客户端导航再走一次。
- 按 URL + 选项缓存,多个组件可复用。
- 合并并发请求。
- 通过 Nitro 推导的 API 类型给响应加上类型。
常用选项:
await useFetch('/api/posts', {
query: { page: 1 },
headers: { 'x-role': 'admin' },
transform: (posts) => posts.map(p => ({ ...p, shortTitle: p.title.slice(0, 32) })),
pick: ['id', 'title'],
watch: [search], // 这些 ref 变化时重新请求
default: () => [], // 首次未返回前的默认值
})
useAsyncData —— 任意异步逻辑
不是简单 fetch(比如 URL 需要计算、要查询数据库、要合并多个来源),用 useAsyncData,并给一个稳定的 key:
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useAsyncData(
() => `post-${route.params.id}`,
() => $fetch(`/api/posts/${route.params.id}`),
{ watch: [() => route.params.id] }
)
</script>
其实 useFetch 就是 useAsyncData(key, () => $fetch(url, opts)) 的薄封装。
useLazyFetch —— 不阻塞导航
useFetch 默认 await,整个页面要等数据就位才渲染。次要内容可以用 useLazyFetch(或 useFetch(..., { lazy: true }))后台加载:
<script setup lang="ts">
const { data, pending } = useLazyFetch('/api/recommendations')
</script>
<template>
<SkeletonList v-if="pending" />
<RecommendationList v-else :items="data" />
</template>
$fetch —— 事件中的直连 HTTP
$fetch 是底层 HTTP 客户端(基于 ofetch),用来在事件处理器、提交动作、服务端路由里做命令式调用。不要拿来当页面主数据源 —— 它不参与 SSR payload。
<script setup lang="ts">
async function createPost () {
return await $fetch('/api/posts', {
method: 'POST',
body: { title: '新文章' },
})
}
</script>
重新拉取
useFetch / useAsyncData 都返回了 refresh():
const { data, refresh } = await useFetch('/api/posts')
async function addPost () {
await $fetch('/api/posts', { method: 'POST', body: {/* … */} })
await refresh()
}
其他组件里想刷新?用同一个 key:
await refreshNuxtData('posts') // 按 key 刷新
await refreshNuxtData() // 全部刷新
服务端路由:数据从哪里来
处理器放在 server/api/,文件名后缀映射 HTTP 方法:
// server/api/posts.get.ts
export default defineEventHandler(() => {
return [{ id: 1, title: 'Hello' }]
})
// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody<{ title: string }>(event)
// 写库 …
return { ok: true }
})
暴露为 /api/posts,页面里用 useFetch('/api/posts') 或 $fetch('/api/posts', ...) 调用。
API 路由的类型
Nuxt 会自动根据 server/ 文件生成类型,useFetch 与 $fetch 都能拿到响应类型:
const { data } = await useFetch('/api/posts') // data: Post[]
想导出响应类型复用:
// server/api/posts.get.ts
export type PostsResponse = Awaited<ReturnType<typeof handler>>
const handler = defineEventHandler(() => fetchPosts())
export default handler
选择原则
- 页面级 SSR 数据? →
useFetch - 自定义异步逻辑 + 稳定缓存? →
useAsyncData - 次要的非阻塞数据? →
useLazyFetch/lazy: true - 用户触发的写操作? →
$fetch+refresh() - 多个组件要同一份数据? → 用相同的 key
照这个路子,页面就能 SSR 到位、预取顺滑、不做重复请求。