首页 / Nuxt 4 入门教程 / 数据获取之 useAsyncData

Nuxt 4 入门教程

数据获取之 useAsyncData

本教程共 50 篇 · 第 23 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

NuxtNuxt4useAsyncData数据获取组合式函数

本节目标:搞懂 useAsyncData 和 useFetch 的本质区别,学会用 handler 包装任意异步逻辑来拿数据。

23-1

上一章讲了 useFetch,它最方便,给个 URL 就够了。但你可能也注意到:useFetch 的底子其实是 useAsyncData——准确说,useFetch(url) 几乎就等于 useAsyncData(url, () => $fetch(url))

一句话区分:

  • useFetch 为你「封装好了怎么发请求」,你只管给 URL。
  • useAsyncData 不管你怎么取数据,你给它一个「取数据的函数」就行,这个函数内部爱用 $fetch、用第三方 SDK、还是读数据库都随你。
Note

当你用的是 CMS、Supabase、Prisma 这类「自带查询层」的工具时,它们往往不希望你直接拼 URL 去 $fetch,而是提供自己的客户端函数。这时候 useFetch 就不好使了,得用 useAsyncData 把那个客户端函数包起来。

23-2

useAsyncData 的第一个参数是一个「键」(key),第二个参数是一个「处理函数」(handler,返回 Promise)。它有两种常见写法:

<script setup lang="ts">
// 写法一:显式给一个 key,再给一个处理函数
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// 写法二:直接给处理函数,key 由 Nuxt 自动生成
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>

那个「键」是用来标识这次请求的,作用很大:

  • 缓存依据:相同 key 的多次调用,会共享同一份数据和状态。
  • 跨组件共享:两个组件用同一个 key,拿到的就是同一份 data
  • 精准刷新:后面章节会讲,刷新时可以指定 key 只刷某一份数据。
Warning

自动生成的 key 只跟你「在哪个文件、哪一行调用」有关。如果你自己封装了一个组合式函数再去包 useAsyncData,不同地方调用会生成不同 key,缓存就乱了。所以自己写封装时,一定要手动给一个明确、唯一的 key

23-3

handler 就是一个返回 Promise 的普通函数,你想写什么写什么。比如同时并行请求两个接口,再拼成一个结果:

<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/api/coupons'),
    $fetch('/api/offers'),
  ])
  return { coupons, offers }
})
// 之后用 discounts.value.coupons 和 discounts.value.offers
</script>

再比如,动态 id 的场景:

<script setup lang="ts">
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})
</script>

23-4

如果你希望「key 随某个响应式值变化、变化了就自动重新请求」,可以用计算属性或 getter 当 key:

<script setup lang="ts">
const userId = ref('123')

const { data: user } = useAsyncData(
  computed(() => `user-${userId.value}`),
  () => fetchUser(userId.value),
)

// 改 userId,数据会自动重抓,旧数据若没人用会被清理
userId.value = '456'
</script>
Tip

当多个组件用同一个 key 调用 useAsyncDatauseFetch,它们共享同一份 dataerrorstatus。但有个前提:handler 函数、transformpickdefault 等「数据相关」的选项必须保持一致,否则开发环境会报警告。而 serverlazyimmediatewatch 这些「行为相关」的选项可以不同。

23-5

useAsyncData 的定位是「获取并缓存数据」,不是「做事」。一个常见错误是把它拿来调 Pinia 的 action:

<script setup lang="ts">
const offersStore = useOffersStore()

// ❌ 不要这样写
// await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>

因为 useAsyncData 可能在缓存命中、重新执行等情况下反复跑,把副作用(改状态、发请求)塞进去会导致意想不到的重复执行。如果真要在初始化时做点事,用 callOnce 工具:

<script setup lang="ts">
const offersStore = useOffersStore()

// ✅ 只执行一次,专门用来触发副作用
await callOnce(() => offersStore.getOffer(route.params.slug))
</script>

23-6

handler 拿回来的原始数据,常常不是你想要的模样。useAsyncData 支持 transform 选项,在数据返回后、交给你之前先做一步映射;也支持 default 选项,给「还没拿到数据时」先放一个占位值。

<script setup lang="ts">
const { data: mountains } = await useAsyncData('mountains', () => $fetch('/api/mountains'), {
  // 只保留标题和描述,去掉冗余字段
  transform: (list) => list.map(m => ({ title: m.title, description: m.description })),
  // 请求完成前,data 先是这个空数组,模板不会因 undefined 报错
  default: () => [],
})
</script>

这两个选项和 useFetch 是通用的。transform 特别适合「接口字段多、页面只用到一小部分」的场景,既能精简数据,也能顺手改类型。default 则在 lazyserver: false 这类「先不取数」的场景下很有用,能避免 dataundefined 引发的渲染异常。

Note

transform 只改变「你拿到手的数据」,并不会减少服务器实际去取的量;要减少 payload 体积,还得配合 pick 或干脆在 server 端就只返回需要的字段。两者作用在不同环节,可以叠加使用。

23-7

useFetch 一样,useAsyncData 出错时不会抛异常中断渲染,而是把错误装进返回的 error 里。你得在模板里判断:

<script setup lang="ts">
const { data, error, status } = await useAsyncData('users', () => $fetch('/api/users'))
</script>

<template>
  <p v-if="error">加载失败:{{ error.message }}</p>
  <ul v-else-if="data">
    <li v-for="u in data" :key="u.id">{{ u.name }}</li>
  </ul>
</template>

如果你希望「出错就直接让 Nuxt 的错误页接管」,也可以在 handler 里手动 throw createError(...)。具体怎么自定义错误页,我们留到后面的错误处理章节细讲。

23-8

前面 useFetch 讲过的返回值,在 useAsyncData 这边一个不少:datapending/statuserrorrefresh/executeclear 全都有。所以两个组合式函数你只要学会一个,另一个几乎不用重新记。

<script setup lang="ts">
const { data, error, refresh } = await useAsyncData('users', () => $fetch('/api/users'))
</script>

<template>
  <button @click="refresh()">刷新</button>
</template>

23-9

给你一个简单的判断:

  • 数据来自一个 HTTP 接口、你只想给个 URL → 用 useFetch,最省事。
  • 你要调用的是某个库自己的查询函数,或要并行组合多个请求、做复杂转换 → 用 useAsyncData
Note

两者共享同一套缓存、同一套返回值、同一套选项(lazyserverwatchtransform 等)。区别只在「谁来决定请求方式」:useFetch 内部用 $fetchuseAsyncData 由你提供 handler。

23-10

useAsyncData 在 Nuxt 3 与 Nuxt 4 中用法一致,无行为差异。若维护 Nuxt 3 老项目,页面文件路径是 pages/xxx.vue 而非 app/pages/xxx.vue,但函数本身照旧。

23-11

useAsyncDatauseFetch 的「更通用版本」。它不绑定 URL,而是接收一个返回 Promise 的 handler,因此能包住任何异步取数逻辑。理解「key 是缓存与共享的核心」这一点,你就能在组件间优雅地复用数据。下一章我们来聊一个更底层的话题:同样一段取数代码,在服务器和浏览器里到底是怎么跑的,以及为什么会出现「水合不匹配」。

23-7 useAsyncData 与 useFetch 的关系

很多初学者会困惑:既然有了 useFetch,为什么还需要 useAsyncData?简单来说,useFetchuseAsyncData 的高层封装。useFetch 内部就是调用了 useAsyncData,同时帮你处理了请求 URL 的拼接和 $fetch 的调用。

当你需要的不只是”请求一个 URL”时,useAsyncData 就派上用场了。比如你想在获取数据前做一些本地计算、调用多个 API 再合并结果、或者使用非 HTTP 的数据源(如本地数据库查询),这些场景都需要 useAsyncData 的灵活性。

选择的原则很简单:如果只是请求一个 API 地址,用 useFetch;如果获取数据的过程涉及额外逻辑,用 useAsyncData。两者在 SSR hydration、缓存、去重等方面的行为完全一致。

23-8 处理复杂的异步数据流

当页面需要从多个 API 获取数据,且这些数据之间有依赖关系时,异步数据流会变得复杂。比如先获取用户信息,再根据用户 ID 获取其订单列表。这种串行依赖的场景,用 async/await 按顺序处理即可。

如果多个数据获取之间没有依赖关系,应该并行执行以节省时间。在 useAsyncData 里,可以用 Promise.all 同时发起多个请求。Nuxt 会等待所有请求完成后,一次性把数据发送给客户端进行 hydration。

对于需要轮询的场景(如实时数据看板),可以结合 setIntervaluseFetchrefresh 方法,定期重新获取数据。记得在组件卸载时清除定时器,避免内存泄漏。