3.2 useAsyncData与useFetch


文档摘要

3.2 useAsyncData 与 useFetch 第一棒跑得好不好,八成取决于取数这步。本节讲 Nuxt 数据获取的两位主角:useAsyncData 与 useFetch——它们是服务端取数、客户端续用的标准通道,也是 payload 交接的主要货源。第 4 章的状态管理、第 6 章的接口调用,都建立在本节的用法之上。 为什么不用裸 fetch 在页面组件里写 ,SSR 时也能取到数据——问题出在交接区:服务端取好的数据怎么告诉浏览器?裸 fetch 没有答案,于是客户端水合时再请求一遍,首屏数据取两次;组件在导航时重复挂载,同一接口被打多次;错误要么吞掉要么炸整页。

3.2 useAsyncData 与 useFetch

第一棒跑得好不好,八成取决于取数这步。本节讲 Nuxt 数据获取的两位主角:useAsyncData 与 useFetch——它们是服务端取数、客户端续用的标准通道,也是 payload 交接的主要货源。第 4 章的状态管理、第 6 章的接口调用,都建立在本节的用法之上。

为什么不用裸 fetch

在页面组件里写 const data = await fetch(url),SSR 时也能取到数据——问题出在交接区:服务端取好的数据怎么告诉浏览器?裸 fetch 没有答案,于是客户端水合时再请求一遍,首屏数据取两次;组件在导航时重复挂载,同一接口被打多次;错误要么吞掉要么炸整页。

useAsyncData 系列在这四个维度补齐:

能力 裸 fetch useAsyncData/useFetch
服务端取数后传给客户端 无,浏览器重复请求 payload 序列化携带,水合直接用
同键去重 相同 key 的并发请求合并
阻塞导航 默认等数据落定再渲染页面
错误与状态管理 手写 error、status、refresh 全套

先看最小用法:

<script setup> // useFetch:useAsyncData 的语法糖,直接给 URL const { data, error, status, refresh } = await useFetch('/api/products', { query: { page: 1, size: 20 }, }) </script> <template> <ul v-if="data"> <li v-for="p in data.list" :key="p.id">{{ p.name }}</li> </ul> <p v-else-if="error">取数失败:{{ error.message }}</p> <p v-else>加载中(status: {{ status }})</p> </template>

两者关系一句话讲清:useFetch(url) 等价于 useAsyncData(url, () => $fetch(url))。需要自定义取数逻辑(多接口聚合、数据加工)时用 useAsyncData:

<script setup> // 聚合两个接口:商品详情 + 库存,一个 key 管理 const { data: product } = await useAsyncData( `product-${useRoute().params.id}`, // key:去重与缓存的身份证 async () => { const id = useRoute().params.id const [info, stock] = await Promise.all([ $fetch(`/api/products/${id}`), $fetch(`/api/stock/${id}`), ]) return { ...info, stock: stock.count } // 返回值进 payload }, ) </script>

key、去重与执行时机

key 是身份。同 key 的 useAsyncData 在同一次渲染里只执行一次:页面里多个组件取同一 key,函数只跑一遍,共享数据。key 也是客户端缓存键,导航往返时命中缓存不重新请求。

执行时机。首次 SSR:服务端执行、数据进 HTML。客户端水合:不执行,直接读 payload。客户端导航到新页面:在客户端执行(此时没有服务器帮忙)。这正是"交接班"模型在数据层的体现——首棒服务器带数据起跑,交接后浏览器自己取新页面的数。

响应性更新。默认只在初始执行,依赖变了不自动重取。两种刷新方式:

<script setup> const page = ref(1) const { data, refresh } = await useFetch('/api/products', { query: { page } }) // 方式一:手动 refresh,页面组件还提供 refreshNuxtData 全局版 function nextPage() { page.value++ refresh() // 用新的 query 重新请求 } // 方式二:watch 选项,query 变化自动重取 // useFetch('/api/products', { query: { page }, watch: [page] }) </script>

选项速查与避坑

const { data } = await useAsyncData(key, handler, { lazy: false, // true:不阻塞导航,先渲染再取(页面需处理空态) server: true, // false:跳过服务端,留给客户端 immediate: true, // false:调用时不执行,等 refresh 触发 watch: [], // 依赖变化自动重取 transform: d => d, // 数据落地前的加工 default: () => [], // data 的初始兜底值 getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key], // 缓存读取策略 })

逐个说容易踩的三个:

lazy 与 await 的配合。默认(非 lazy)配合顶层 await,页面等数据到齐再渲染,SEO 完整;lazy: true 时函数立即返回,页面先出骨架,数据到了再补——适合登录后的次级页面。注意 lazy 模式下不要直接解构使用 data.value 的字段,要用默认值兜住空态:

<script setup> const { data: list } = useFetch('/api/feed', { lazy: true, default: () => [], // 空态兜底,模板不会炸 }) </script>

server: false 的语义。数据只在客户端取:适合个性化内容(用户偏好、地理位置)——这些内容每个用户不同,服务端渲染了也无法缓存,反而拖慢 TTFB。

**fetch 与 useFetch 的选择**。事件处理器里(点击按钮提交表单)用 `fetch`,它就是一次普通请求;组件 setup 里为页面供数用 useFetch,它管交接。在 setup 里裸用 $fetch 等于放弃 payload 通道,回到重复请求的老路。

⚠️ 常见坑:key 缺失或全站同名。不写 key 时 Nuxt 依文件与代码位置自动生成,代码挪动后 key 变化可能引发莫名的重复请求;而多处硬编码同一个 key,会让不同页面的数据互相覆盖。规范做法:显式命名,带业务前缀与参数。

💡 关键直觉:useAsyncData 的本质是"把异步数据的所有权交给框架"——你声明 key 与函数,框架决定何时跑、跑几遍、结果怎么传到浏览器。所有权交出去,交接的坑就归框架管了。

本节要点回顾

  • 关系:useFetch 是 useAsyncData 加 $fetch 的语法糖,自定义逻辑时退回 useAsyncData;
  • 四个增量:payload 交接、同 key 去重、阻塞导航、错误状态管理——裸 fetch 都没有;
  • 执行时机:SSR 服务端跑一次、水合读 payload、客户端导航在浏览器跑;
  • 刷新三法:手动 refresh、watch 依赖、refreshNuxtData 全局按 key 清;
  • lazy 空态:lazy 模式必配 default 兜底,否则模板在数据到达前就崩;
  • 分工口诀:页面供数用 useFetch,事件交互用 $fetch。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U