12k
All articles

Nuxt 中的服务端数据获取

Nuxt 服务端数据获取的 payload 注水、key 管理与 dedupe 行为规则,可有效避免重复请求和注水错误。

OpenReplay Team
OpenReplay Team
Nuxt 中的服务端数据获取

如果你正在构建一个 Nuxt 应用,并且疑惑为什么数据会被获取两次——或者为什么共享相同 key 的组件会出现意外行为——你并不孤单。Nuxt SSR 数据获取有一些特定的规则,即使是经验丰富的开发者也容易踩坑。

本文将解释 useAsyncDatauseFetchNuxt 4 中的工作原理,涵盖 payload 水合(hydration)、导航行为、key 管理,以及最容易引起混淆的陷阱。

核心要点

  • Nuxt 在服务端运行 useFetchuseAsyncData,将响应序列化到 HTML payload 中,并在客户端进行水合而无需重新获取
  • 共享相同 key 的组件会共享相同的响应式状态——为独立的数据实例使用不同的 key
  • 在当前的 Nuxt 4 版本中,某些选项(handler、transform、pick、getCachedData、default、deep)在共享 key 的调用之间必须匹配
  • 使用 useFetchuseAsyncData 进行 SSR 安全的数据获取;将 $fetch 保留给事件处理器和仅客户端代码

Nuxt 如何执行服务端数据获取

当你在页面或组件中调用 useFetchuseAsyncData 时,Nuxt 会在初始请求期间在服务端运行该获取操作。服务器将响应序列化为嵌入在 HTML 中的 payload。当客户端进行水合时,它会读取这个 payload 而不是重新获取——从而消除重复的网络请求。

const { data } = await useFetch('/api/products')

这一行代码在服务端执行,将结果嵌入页面,并在客户端无缝水合。没有双重获取,没有水合不匹配。

阻塞式获取 vs. 懒加载获取

默认情况下,Nuxt 会阻塞导航,直到 awaited 的数据获取完成。这确保你的页面在渲染时数据已经可用。

对于客户端导航,你可以选择懒加载获取:

const { data, status } = useLazyFetch('/api/comments')

使用懒加载获取时,导航默认不会阻塞,获取操作在后台运行。你需要在模板中使用 status ref 来处理加载状态。

理解 Nuxt 数据获取的 Key

Key 是 Nuxt 缓存和去重请求的核心机制。每个 useFetch 调用使用 URL 作为其默认 key。对于 useAsyncData,你需要显式提供 key,或让 Nuxt 为你生成一个确定性的 key。

这里是关键部分:共享相同 key 的组件会共享相同的状态。这包括 dataerrorstatuspending refs。

// 组件 A
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

// 组件 B - 与组件 A 共享状态
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

两个组件接收相同的响应式 refs。改变一个,另一个也会反映出来。

Key 一致性规则

在当前的 Nuxt 4 版本中,当多个调用共享一个 key 时,Nuxt 会强制某些选项保持一致。这些选项必须匹配:

  • Handler 函数
  • transform 函数
  • pick 数组
  • getCachedData 函数
  • default
  • deep 选项

这些选项可以安全地不同:

  • server
  • lazy
  • immediate
  • dedupe
  • watch

违反一致性会触发开发警告和不可预测的行为。

安全的 Key 策略

对于路由特定的数据,在 key 中包含路由参数:

const route = useRoute()
const { data } = await useAsyncData(
  `product-${route.params.id}`,
  () => $fetch(`/api/products/${route.params.id}`)
)

对于不应共享状态的独立实例,使用不同的 key:

const { data: sidebar } = await useAsyncData('users-sidebar', fetchUsers)
const { data: main } = await useAsyncData('users-main', fetchUsers)

Nuxt 数据缓存和去重行为

Nuxt 会自动去重具有匹配 key 的并发请求。如果三个组件同时请求相同的 key,只会触发一个网络请求。

dedupe 选项控制刷新行为:

const { data, refresh } = await useFetch('/api/data', {
  dedupe: 'cancel' // 在启动新请求之前取消待处理的请求
})

在 Nuxt 4.2 及更高版本中,取消支持得到了显著改进。当支持时,在快速导航期间,来自先前路由的过时响应可以被取消或忽略,减少过时数据短暂出现的风险。

更多详情:https://nuxt.com/docs/api/composables/use-fetch

常见陷阱

混淆 Nuxt 的 useFetch 与其他库

Nuxt 的 useFetch 不同于 @vueuse/coreuseFetch 或类似工具。Nuxt 的版本会自动处理 SSR payload 水合。使用其他库的 useFetch 会完全绕过这一机制,导致双重获取和水合不匹配。

在 Setup 中不使用 useAsyncData 而直接使用 $fetch

<script setup> 中直接调用 $fetch 会在服务端和客户端都运行:

// ❌ 获取两次
const data = await $fetch('/api/users')

// ✅ 获取一次,正确水合
const { data } = await useFetch('/api/users')

$fetch 保留给事件处理器和仅客户端交互。

使用冲突选项复用 Key

这会触发警告和 bug:

// ❌ deep 选项冲突
await useAsyncData('users', fetchUsers, { deep: false })
await useAsyncData('users', fetchUsers, { deep: true })

在 server: false 时期望水合前有数据

当你设置 server: false 时,数据在水合完成之前会保持 null——即使你 await 了该组合式函数。

结论

Nuxt 4 的数据获取模型以服务端执行、payload 水合和基于 key 的缓存为中心。保持 key 稳定且每个数据源唯一。在组件之间共享 key 时确保选项一致性。使用 useFetchuseAsyncData 进行 SSR 安全的获取,将 $fetch 保留给客户端交互。

掌握这些模式,你就能避免困扰大多数 Nuxt 开发者的双重获取和状态共享 bug。

常见问题

为什么我的数据在 Nuxt 中会获取两次?

这通常发生在你在 script setup 中直接使用 $fetch 而不是 useFetch 或 useAsyncData 时。直接的 $fetch 调用会在服务端和客户端都运行。将你的获取操作包装在 useFetch 或 useAsyncData 中以利用 payload 水合,这样只在服务端获取一次并在客户端复用该数据。

我应该在什么时候使用 useAsyncData 而不是 useFetch?

当直接从 URL 获取数据时使用 useFetch——它会根据 URL 自动处理 key。当你需要自定义逻辑、组合多个 API 调用或显式控制缓存 key 时使用 useAsyncData。两者都提供相同的 SSR 水合优势。

如何防止组件共享获取的数据?

共享相同 key 的组件会共享相同的响应式状态。要保持数据独立,为每个组件使用唯一的 key。例如,当两个组件获取相同的端点但需要独立状态时,使用 users-sidebar 和 users-main 而不是只用 users。

useFetch 中的 dedupe 选项有什么作用?

dedupe 选项控制 Nuxt 如何处理多个刷新调用。将 dedupe 设置为 cancel 会在启动新请求之前中止任何待处理的请求。这有助于避免快速用户交互期间的竞态条件,并确保在支持取消时较新的响应优先。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.