数据获取
约 1276 字大约 4 分钟
2026-01-29
重要
- 页面首屏数据:优先
useFetch/useAsyncData - 事件触发请求:优先
$fetch - 先区分 "是否参与 SSR 首屏",再选 API
最佳实践
- 页面首屏请求不要直接写
$fetch,避免 SSR + CSR 双请求 - 给关键请求设置稳定
key,方便缓存复用和主动刷新 - 路由参数或查询参数会变化时,使用响应式
key或watch - 多接口并发请求优先
useAsyncData + Promise.all - 统一在请求拦截中处理 token 注入和 401 跳转
- 服务端请求依赖登录态时,显式透传
cookie/authorization - 事件场景(提交表单、点击加载更多)使用
$fetch,不要滥用useFetch
核心概念
Nuxt 数据获取本质上有两类场景:首屏数据与事件请求,前者要参与 SSR,后者只在交互时发生,这也是为什么 Nuxt 提供三套 API(useFetch / useAsyncData / $fetch)
笔记
useFetch/useAsyncData的结果会进入 AsyncData 缓存- 在页面
setup的首屏数据场景中直接用$fetch,通常会出现服务端一次、客户端再一次
| API | 推荐场景 | 是否参与 SSR |
|---|---|---|
useFetch | 页面首屏 HTTP 请求 | 是 |
useAsyncData | 组合请求 / 自定义异步逻辑 | 是 |
$fetch | 点击提交、局部更新、懒触发 | 否(默认不用于首屏) |
三种 API
在 SSR 首次请求时 useFetch/useAsyncData 会在服务端执行并参与 HTML 渲染,后续在客户端 Hydration 时会复用 SSR 结果,避免同一请求再次发起,而当客户端路由切换会按 Nuxt 规则重新触发对应页面的数据获取
useFetch
useFetch 适合 "请求一个 HTTP 接口并渲染页面" 的常规场景,写法最直接
<script setup lang="ts">
interface PostItem {
id: number
title: string
}
const page = ref(1)
const keyword = ref('')
const { data, status, error, refresh } = await useFetch<PostItem[]>('/api/posts', {
query: { page, q: keyword },
key: () => `posts:${page.value}:${keyword.value}`,
watch: [page, keyword],
})
</script>
<template>
<section>
<p v-if="status === 'pending'">加载中...</p>
<p v-else-if="error">请求失败:{{ error.message }}</p>
<ul v-else>
<li v-for="post in data" :key="post.id">{{ post.title }}</li>
</ul>
<button @click="page++">下一页</button>
<button @click="refresh">手动刷新</button>
</section>
</template>| 选项 | 作用 | 常见值 / 用法 |
|---|---|---|
key | 指定缓存键,控制复用与刷新粒度 | 'posts:list'、() => posts:${id} |
watch | 监听依赖变化后自动重新请求 | [page, keyword] |
server | 是否在服务端执行 | true(默认)/ false(仅客户端) |
lazy | 是否阻塞路由渲染 | false(默认)/ true |
transform | 响应后统一转换数据 | (input) => normalize(input) |
pick | 只提取响应中的部分字段 | ['list', 'total'] |
dedupe | 并发请求去重策略 | 'cancel' / 'defer' |
useFetch 常用选项
useAsyncData
当需要 "并发多个请求 + 自定义组合返回" 时,useAsyncData 比 useFetch 更灵活
<script setup lang="ts">
const route = useRoute()
const { data, status, error, refresh } = await useAsyncData(
() => `dashboard:${route.params.id}`,
async () => {
const [profile, stats, notices] = await Promise.all([
$fetch('/api/profile'),
$fetch('/api/stats'),
$fetch('/api/notices'),
])
return { profile, stats, notices }
},
{
watch: [() => route.params.id],
},
)
</script>$fetch
$fetch 更适合事件触发场景:提交、删除、点赞、切换开关等,它不会自动纳入 AsyncData 缓存体系,所以常与 refreshNuxtData 搭配
笔记
refreshNuxtData 的 key 来自 useFetch/useAsyncData,不是来自 $fetch 调用本身
<script setup lang="ts">
const submitting = ref(false)
const FEEDBACK_LIST_KEY = 'feedback-list'
// 这里先注册一个首屏数据 key,供后续 refreshNuxtData 精准刷新
const { data: feedbackList } = await useAsyncData(
FEEDBACK_LIST_KEY,
() => $fetch('/api/feedback'),
)
async function submitFeedback(content: string) {
submitting.value = true
try {
await $fetch('/api/feedback', {
method: 'POST',
body: { content },
})
// 提交成功后,刷新同 key 的 AsyncData 缓存
await refreshNuxtData(FEEDBACK_LIST_KEY)
}
finally {
submitting.value = false
}
}
</script>使用策略
缓存与刷新
常见顺序是先用 useNuxtData复用缓存,数据变更后调用 refreshNuxtData,需要彻底失效时再执行 clearNuxtData
| API | 作用 | 常见用法 |
|---|---|---|
useNuxtData | 读取已存在的 AsyncData 缓存,不触发请求 | const { data } = useNuxtData(key) |
refreshNuxtData | 重新执行对应 useFetch/useAsyncData 的 handler,传 key 时只刷新指定缓存 | await refreshNuxtData(key) |
clearNuxtData | 清空缓存并强制下次重取 | clearNuxtData(key) |
缓存与刷新相关 API
<script setup lang="ts">
const { data: cachedPosts } = useNuxtData('posts:list')
async function reloadPosts() {
await refreshNuxtData('posts:list')
}
function resetPostsCache() {
clearNuxtData('posts:list')
}
</script>服务端透传身份信息
用户登录后,后端通过 Set-Cookie 下发 Cookie ,浏览器会自动保存。后续页面走 SSR 渲染时,如果服务端请求需要用户身份,则可用 useRequestHeaders 从当前请求读取 cookie/authorization 并透传给后端接
笔记
token 响应体是否保存取决于业务代码(如 useCookie、localStorage 等)
<script setup lang="ts">
const headers = useRequestHeaders(['cookie', 'authorization'])
const { data } = await useFetch('/api/me', {
headers,
})
</script>请求拦截
const { data } = await useFetch('/api/user', {
onRequest({ options }) {
const token = useCookie<string | null>('token')
if (!token.value) return
const headers = new Headers(options.headers)
headers.set('Authorization', `Bearer ${token.value}`)
options.headers = headers
},
async onResponseError({ response }) {
if (response.status === 401 && import.meta.client) {
await navigateTo('/login')
}
},
})