useAsyncData
useAsyncData 是 useFetch 的底层组合式函数,提供了更灵活的异步数据获取方式。理解 useAsyncData 能帮你理解 Nuxt 数据获取的完整机制,也能在 useFetch 不够灵活时提供替代方案。
什么时候用 useAsyncData 而不是 useFetch?
| 场景 | 用 useFetch | 用 useAsyncData |
|---|---|---|
| 标准 API 请求 | ✅ | 也能用,但更繁琐 |
| 聚合多个 API | ❌ 一个 useFetch 只能请求一个 URL | ✅ 在获取函数中发多个请求 |
| 使用第三方 SDK(Supabase、Firebase) | ❌ | ✅ 包裹 SDK 的获取方法 |
| 需要自定义获取逻辑 | ❌ | ✅ 获取函数完全自定义 |
| 需要复杂的条件判断 | 有限的 immediate + watch | ✅ 获取函数内任意逻辑 |
| 调用 Nuxt 内部 API | ❌ | ✅ 如 queryContent() |
简单记忆
useFetch = useAsyncData + $fetch。90% 的情况用 useFetch 就够了。只有在需要更灵活的获取逻辑时才用 useAsyncData。
基本用法
const { data, status, error, refresh, clear } = await useAsyncData(
'users', // key(必须手动指定)
() => $fetch('/api/users') // 获取函数
)与 useFetch 的三个关键区别
- 必须手动指定 key(
useFetch自动根据 URL 生成) - 必须手动提供获取函数(
useFetch自动用$fetch) - 获取函数可以是任何异步操作(不限于 HTTP 请求)
返回值
与 useFetch 完全一致:
| 属性 | 类型 | 说明 |
|---|---|---|
data | Ref<T | undefined> | 响应数据 |
status | Ref<string> | 状态:idle/pending/success/error |
error | Ref<Error | undefined> | 错误信息 |
pending | Ref<boolean> | 是否正在加载 |
refresh | (opts?) => Promise<void> | 刷新数据 |
execute | (opts?) => Promise<void> | refresh 的别名 |
clear | () => void | 清除数据 |
refresh 和 execute 的区别
没有区别,完全相同。execute 这个名字在 immediate: false 场景下语义更清晰——"执行"请求。
key 的重要性与最佳实践
key 是 useAsyncData 最关键的概念,它决定了数据的去重、缓存、SSR 传递:
key 的三大作用:
├── 1. 去重:多个组件使用相同 key → 共享数据,不重复请求
├── 2. SSR 传递:服务端获取的数据通过 key 存入 Payload
└── 3. 缓存:通过 key 访问已缓存的数据key 的命名规则
// ✅ 好的 key:语义清晰、唯一
'useAsyncData'('app-config', ...)
'useAsyncData'('dashboard-stats', ...)
'useAsyncData'('user-profile', ...)
// ❌ 坏的 key:太宽泛,可能冲突
'useAsyncData'('data', ...)
'useAsyncData'('list', ...)动态 key 的正确用法
// ✅ 动态 key:包含参数信息,确保不同参数不共享缓存
const { data } = await useAsyncData(
`users-page-${page.value}`, // page 变化时生成新 key
() => $fetch('/api/users', { query: { page: page.value } }),
{ watch: [page] }
)
// ❌ 完全静态的 key:不同参数共享缓存,导致数据错乱
const { data } = await useAsyncData(
'users', // 永远是同一个 key
() => $fetch('/api/users', { query: { page: page.value } }),
{ watch: [page] }
)INFO
️ 动态 key 的陷阱:key 变化时 useAsyncData 会创建新的数据实例,旧数据被丢弃。如果你在 watch 中监听了参数变化,同时 key 也应该包含这些参数,否则会出现 key 不变但数据变了的不一致状态
最佳实践
key 中包含所有影响数据的变量。如果数据依赖 page 和 keyword,key 应该是 'users-' + page.value + '-' + keyword.value。
SSR 中的执行时序
理解 useAsyncData 在 SSR 和客户端的行为差异,是避免 bug 的关键:
首次访问(SSR)
1. 用户请求 /dashboard
↓
2. 服务端执行 useAsyncData 获取函数
↓
3. 数据通过 key 存入 nuxtApp.payload.data
↓
4. 服务端用数据渲染 HTML
↓
5. HTML + Payload 一起发送到浏览器
↓
6. 客户端 Hydration 时,从 Payload 读取数据(不重新请求)客户端导航(页面跳转)
1. 用户点击 <NuxtLink to="/settings">
↓
2. 客户端执行 useAsyncData 获取函数
↓
3. 数据在客户端获取(发 HTTP 请求)
↓
4. 页面更新关键理解
- SSR 首次加载:数据在服务端获取,客户端直接使用 Payload,不重复请求
- 客户端导航:数据在客户端获取,发起新请求
useAsyncData自动处理这两种情况,你不需要写条件判断
server: false 时的行为
const { data } = await useAsyncData('client-only-data', () => $fetch('/api/me'), {
server: false, // SSR 时不获取
})SSR 时:跳过获取函数,data 为 undefined
↓
客户端 Hydration 后:发起请求获取数据
↓
data 变为实际值INFO
️ server: false 时 data 在 SSR 为 undefined 需要用 default 设置默认值,或在模板中处理空状态
const { data } = await useAsyncData('me', () => $fetch('/api/me'), {
server: false,
default: () => ({ name: '', email: '' }), // 避免模板中 data 为 undefined
})选项
useAsyncData 的选项与 useFetch 几乎一样(因为 useFetch 就是 useAsyncData + $fetch 的封装):
| 选项 | 默认值 | 说明 |
|---|---|---|
server | true | 是否在服务端执行 |
lazy | false | 是否不阻塞导航 |
immediate | true | 是否立即执行 |
default | undefined | data 的默认值函数 |
transform | undefined | 转换响应数据 |
pick | undefined | 只提取指定字段 |
watch | undefined | 监听源变化自动刷新 |
dedupe | 'cancel' | 去重策略:cancel 或 defer |
deep | false | 是否深度 ref(返回数据的嵌套对象也是响应式的) |
getCachedData | undefined | 自定义缓存读取逻辑 |
唯一区别
useAsyncData 没有 method、query、body、headers、baseURL 等 HTTP 选项(这些在获取函数中自己处理)。
deep 选项详解
// 默认:deep: false
const { data } = await useAsyncData('user', () => $fetch('/api/user'))
// data.value 是浅响应式:顶层属性变化触发更新,嵌套属性变化不触发
data.value.name = 'new' // ✅ 触发更新
data.value.settings.theme = 'dark' // ❌ 不触发更新
// 开启深度响应式
const { data } = await useAsyncData('user', () => $fetch('/api/user'), {
deep: true,
})
data.value.settings.theme = 'dark' // ✅ 触发更新INFO
️ 性能提示:deep: true 会递归遍历数据对象创建响应式代理 数据量大时可能影响性能。大多数情况下 deep: false 够用,如果需要修改嵌套数据,用 triggerRef(data) 或重新赋值
getCachedData 自定义缓存
const { data } = await useAsyncData('posts', () => $fetch('/api/posts'), {
getCachedData(key, nuxtApp) {
const cached = nuxtApp.payload.data[key]
if (!cached) return undefined
// 5 分钟内使用缓存
const expirationDate = new Date(cached.fetchedAt)
expirationDate.setMinutes(expirationDate.getMinutes() + 5)
if (new Date() < expirationDate) {
return cached // 返回缓存数据,不发请求
}
return undefined // 缓存过期,重新请求
},
})getCachedData 的工作原理
如果这个函数返回了数据,useAsyncData 就不会执行获取函数,直接使用返回的缓存数据。这是实现自定义缓存策略的核心。
使用场景
1. 聚合多个 API
const { data } = await useAsyncData('posts-page', async () => {
const [posts, categories] = await Promise.all([
$fetch('/api/posts'),
$fetch('/api/categories'),
])
return { posts, categories }
})为什么不用两个 useFetch?
也可以,但 useAsyncData 的好处是:
- 只有一个 key,管理更简单
- 可以在获取函数中处理两个请求之间的依赖关系
- 一次
refresh()刷新所有数据
2. 依赖请求
const { data: categories } = await useFetch('/api/categories')
const selectedCategory = ref(categories.value?.[0]?.id)
// 第二个请求依赖第一个的结果
const { data: products } = await useAsyncData(
'products-by-category',
() => $fetch('/api/products', {
query: { categoryId: selectedCategory.value },
}),
{ watch: [selectedCategory] }
)3. 使用第三方 SDK
import { supabase } from '~/server/utils/supabase'
const { data } = await useAsyncData('profile', () =>
supabase.from('profiles').select('*').eq('id', userId).single()
)为什么第三方 SDK 要用 useAsyncData?
useFetch 只能用于 HTTP 请求。Supabase、Firebase 等 SDK 有自己的获取方式,用 useAsyncData 包裹后也能享受 SSR 数据传递、去重、缓存等好处。
4. 使用 Nuxt Content
const { data } = await useAsyncData('home-content', () =>
queryContent('/').findOne()
)5. 仪表盘数据聚合
const { data } = await useAsyncData('dashboard', async () => {
const [stats, recentOrders, notifications] = await Promise.all([
$fetch('/api/stats'),
$fetch('/api/orders/recent'),
$fetch('/api/notifications'),
])
return { stats, recentOrders, notifications }
})createUseAsyncData(v4.4+)
类似 createUseFetch,创建自定义的 useAsyncData 实例,预设默认选项:
// app/composables/useApiAsyncData.ts
export const useApiAsyncData = createUseAsyncData((options) => {
return {
...options,
server: false, // 默认不在服务端获取
deep: false,
}
})
// 使用:和 useAsyncData 一样,但默认 server: false
const { data } = await useApiAsyncData('me', () => $fetch('/api/me'))useNuxtData
通过 key 访问其他地方获取的缓存数据,不触发新请求:
// 在布局中获取全局配置
const { data: config } = await useAsyncData('app-config', () => $fetch('/api/config'))
// 在子组件中直接使用缓存(不再请求)
const { data: config } = useNuxtData('app-config')useNuxtData 的典型场景
- 根组件获取全局配置,子组件通过
useNuxtData使用 - 中间件中获取数据,页面中直接使用
- 避免多个组件重复请求同一个 API
INFO
️ 如果 key 对应的数据不存在useNuxtData 返回 undefined。确保数据已经被获取过再使用
useAsyncData vs useFetch 完整对比
| 特性 | useFetch | useAsyncData |
|---|---|---|
| 获取方式 | 固定用 $fetch | 自定义获取函数 |
| key | 自动生成(基于 URL) | 必须手动指定 |
| HTTP 选项 | method/query/body 等 | 无(在获取函数中处理) |
| 类型推断 | ✅ 基于自动生成 | ⚠️ 需要手动指定或依赖推断 |
| 适用场景 | 标准 API 请求 | 自定义获取逻辑 |
| 底层实现 | 封装了 useAsyncData | 底层 API |
选择策略
- 需要发 HTTP 请求?→ 先试
useFetch useFetch不够灵活?→ 用useAsyncData+$fetch- 不是 HTTP 请求?→ 用
useAsyncData
常见问题
key 冲突导致数据错乱
不同页面使用相同 key 会导致数据互相覆盖:
// ❌ 页面 A 和页面 B 都用 'items' 作为 key
// 页面 A 获取用户列表,页面 B 获取商品列表
// 客户端导航时可能看到 A 的数据在 B 页面显示
const { data } = await useAsyncData('items', () => $fetch('/api/users'))
// ✅ 使用有语义的 key
const { data } = await useAsyncData('users-list', () => $fetch('/api/users'))获取函数中有副作用
// ❌ 错误:获取函数有副作用
const { data } = await useAsyncData('cart', async () => {
await $fetch('/api/cart/clear') // 副作用:清空购物车!
return $fetch('/api/cart')
})
// ✅ 正确:获取函数只获取数据
const { data } = await useAsyncData('cart', () => $fetch('/api/cart'))INFO
️ 获取函数可能被执行多次(SSR 一次、客户端导航时又执行) 副作用操作(如写入、删除)会被重复执行。获取函数应该是纯读取操作
SSR 时数据未传递到客户端
- 确认
server: true(默认值) - 确认获取函数在服务端能正常执行(不依赖浏览器 API)
- 确认返回值是可序列化的(纯对象/数组,不能是函数、类实例)
知识脉络
useFetch → 你在这里:useAsyncData
│
├─→ 下一步:$fetch(非响应式请求)
│
├─→ 深入了解:缓存与刷新(key 的缓存机制)
│
└─→ 深入了解:SSR 数据传递(Payload 如何传递数据)