☰
Nuxt 数据获取指南:useLazyFetch 非阻塞导航请求完全解析
2026/11/5 20:41:57 网站建设 项目流程

Nuxt 数据获取指南:useLazyFetch 非阻塞导航请求完全解析

【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt

useLazyFetch是 Nuxt 官方基于useFetch封装的"懒加载"数据获取 composable,它通过内置开启lazy: true选项,让客户端导航在异步请求完成前就立即执行,数据在后台继续拉取。本文将从源码实现、API 签名、参数与返回值、加载/错误状态处理、以及真实测试用例五个维度,完整讲解在 Nuxt 应用中如何用useLazyFetch构建不阻塞路由切换的 SSR 友好数据流,同时剖析它与await、useFetch、useAsyncData之间的边界关系,帮助你在正确的场景下做出正确的选择。

它解决了什么问题:阻塞式导航与懒加载

在 Nuxt 中,默认的useFetch会通过 Vue 的<Suspense>机制阻塞客户端导航,直到其异步 handler 全部 resolve 之后,用户才会真正进入新页面——用户停留在旧页面(期间可配合进度条),然后直接落在"数据已就绪"的新页面上。这在用户体验上表现为"等待后再进入"。

而useLazyFetch允许导航立即继续,数据在后台获取,页面先渲染出加载态(loading state),数据到达后再更新 UI。

useLazyFetch本质就是useFetch设置了lazy: true。官方文档对此的表述是:useLazyFetch通过在内部将lazy选项置为true,提供了对useFetch的封装,使其在 handler 被 resolve 之前就触发导航。

在源码层面,这一点可以得到精确验证。Nuxt 使用createUseFetch工厂函数生成了useFetch与useLazyFetch两个实例,两者共享同一套实现逻辑,唯一的差别是工厂默认选项不同。见 fetch.ts 工厂与实例定义:

export const useFetch: UseFetch = (createUseFetch as unknown as { __nuxt_factory: typeof createUseFetch }).__nuxt_factory() export const useLazyFetch: UseFetch = (createUseFetch as unknown as { __nuxt_factory: typeof createUseFetch }).__nuxt_factory({ lazy: true, // @ts-expect-error private property _functionName: 'useLazyFetch', }) as ReturnType<typeof createUseFetch>

可以看到,useLazyFetch就是向createUseFetch工厂传入了{ lazy: true },同时注入内部属性_functionName: 'useLazyFetch'(该属性仅用于开发模式下报错信息与诊断提示,便于 Nuxt 识别调用来源)。

需要特别指出的是,懒加载在服务器端渲染(SSR)期间依然会阻塞渲染。lazy只影响客户端导航行为:在首屏 SSR 阶段,无论是否lazy,Nuxt 都会在序列化页面 HTML 之前等待请求完成(底层通过<Suspense>与onServerPrefetch实现),因此返回给浏览器的首屏 HTML 始终包含完整数据。

基本用法:一个最小可运行示例

与useFetch一样,useLazyFetch应在<script setup>、插件(plugin)或路由中间件(middleware)中调用。

<script setup lang="ts"> const { status, data: posts } = await useLazyFetch('/api/posts') </script> <template> <div v-if="status === 'pending'"> Loading ... </div> <div v-else-if="status === 'error'"> Error loading posts </div> <div v-else> <div v-for="post in posts"> <!-- do something --> </div> </div> </template>

关于代码中的await需要给出精确的行为预期(这一点即使在官方文档中也常被误解):

  • 在服务器端:await会照常等待请求,SSR 渲染会被阻塞直至数据就绪,HTML 中始终包含完整数据;
  • 在客户端导航时:对lazy调用执行await会"立即 resolve"——注意它并不会真的等待请求完成。data在await之后仍处于默认值(即undefined或default()工厂的返回值),因此你必须通过status在模板中自行处理加载态与错误态。

也就是说,await与lazy是相互独立的两件事。若你希望在客户端导航时真正等待数据到达再进入页面,应当去掉lazy选项(即改用useFetch),而不是依赖await。这一要点在入门指南数据获取中有专门的警告强调。

与useFetch({ lazy: true })、不await三者之间的差别

从用户可感知的行为来看,以下三种写法"看起来"相似,但底层语义并不相同:

写法请求发起时机客户端导航SSR 行为
useFetch(url)(默认,已await)setup 期间立即发起阻塞导航直到数据就绪等待数据后输出完整 HTML
useFetch(url, { lazy: true })组件挂载(mounted)后发起立即导航,后台拉取等待数据后输出完整 HTML
useFetch(url)(不await)setup 期间立即发起立即导航,后台拉取等待数据后输出完整 HTML
useLazyFetch(url)组件挂载后发起立即导航,后台拉取等待数据后输出完整 HTML

关键在于:不await只是"不等待",请求仍在 setup 阶段就开始;而lazy(以及useLazyFetch)是把请求真正推迟到组件挂载时再发起,意图更明确、语义更自洽。因此官方建议:想要非阻塞行为时,优先使用useLazyFetch/useLazyAsyncData或在选项中显式写lazy: true。

useLazyFetch拥有与useFetch完全一致的签名(signature),差异仅在于内部已把lazy预设为true。

类型签名(Signature)

export function useLazyFetch<ResT, ErrorT = NuxtError<unknown>, DataT = ResT> ( url: string | Request | Ref<string | Request> | (() => string | Request), options?: UseFetchOptions<ResT, DataT>, ): AsyncData<DataT, ErrorT> & Promise<AsyncData<DataT, ErrorT>>

需要理解的关键点是返回值AsyncData<DataT, ErrorT> & Promise<AsyncData<DataT, ErrorT>>:返回对象同时具备 Promise 能力,因此你可以:

  • await它:Promise 会在请求(在 SSR 下)完成后 resolve;
  • 直接解构而不await:data可能在一段时间内为undefined;
  • 安全地解构 Promise 的方法:then、catch、finally都是可枚举且可解构的(该能力由 asyncData.ts 中的Object.defineProperties保证)。

由于AsyncData是 Promise 与响应式对象融合的特殊类型,即使你不写await,解构出的status、error、data也始终是 Vue refs,可在模板中直接响应式使用,而在<script setup>内访问时需通过.value。

参数详解

useLazyFetch接受的参数与useFetch完全一致,lazy已自动置为true:

  • url(string | Request | Ref<string | Request> | () => string | Request):要请求的 URL 或 Request 对象。支持传入 ref 或返回字符串/Request 的函数,从而支持动态端点。

  • options:请求配置对象,完整定义可参考useFetch的参数说明。所有选项都可以是静态值、ref或 computed 值。

useLazyFetch常用且高频的选项包括(完整列表见useFetch文档的 Options 表格):

选项类型默认值作用
keyMaybeRefOrGetter<string>自动生成用于去重的唯一 key;缺省时由 URL、options 与源码调用位置组合哈希生成
query/paramsMaybeRefOrGetter<SearchParams>-追加到 URL 的查询参数,对象会被自动序列化
serverbooleantrue是否在服务器端发起请求;设为false可做纯客户端获取
immediatebooleantrue设为false可阻止请求立即发起,配合execute()手动触发
default() => DataT-异步数据到达前data的默认值工厂函数,配合useLazyFetch尤其有用
transform(input) => DataT-对 resolve 后的结果进行转换(不可与pick同时使用)
pickstring[]-只从结果中挑选指定字段,可减小 Nuxt payload 体积(不可与transform同时使用)
watchMultiWatchSources \| false-额外 watch 的响应式源数组,变化时自动刷新
dedupe'cancel' \| 'defer''cancel'避免同一 key 并发重复请求:cancel取消旧请求,defer复用进行中的请求
enabledMaybeRefOrGetter<boolean>true是否允许请求执行的"闸门",为false时所有执行都被拦截
getCachedData函数见下自定义缓存读取策略,返回undefined时触发真实请求

为data提供默认值:懒加载的黄金搭档

由于懒加载下data在初始阶段就是默认值,若你想避免模板中的空值判断,可以配合default选项。仓库测试用例 use-fetch.test.ts 精确验证了这一行为:

it('should use default value with lazy', () => { const { data, pending } = useLazyFetch<TestData>('/api/test', { default: () => ({ method: 'default', headers: {} }) }) expect(pending.value).toBe(true) expect(data.value).toEqual({ method: 'default', headers: {} }) expect(data.value).not.toBeNull() expect(data.value.method).toEqual('default') })

测试断言了三点:请求发出后pending为true、data立即等于default()返回的默认对象而非null/undefined、默认值可被安全访问。

getCachedData默认实现

const getDefaultCachedData = (key, nuxtApp, ctx) => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]

该默认缓存仅当在nuxt.config中开启experimental.payloadExtraction时才生效。

返回值(AsyncData 对象)

useLazyFetch返回与useFetch相同的AsyncData对象,解构出的成员如下:

名称类型说明
dataRef<DataT \| undefined>异步请求的结果。懒加载模式下请求完成前为默认值
refresh(opts?: AsyncDataExecuteOptions) => Promise<void>手动刷新数据。默认情况下 Nuxt 会等待上一次refresh完成才允许下一次执行
execute(opts?: AsyncDataExecuteOptions) => Promise<void>refresh的别名
errorRef<ErrorT \| undefined>请求失败时的错误对象
statusRef<'idle' \| 'pending' \| 'success' \| 'error'>请求状态,用于区分四种生命周期状态
pendingRef<boolean>请求进行中为true。若开启experimental.pendingWhenIdle,则status为idle且无缓存数据时也为true
clear() => void重置data为undefined(或options.default()的返回值)、error为undefined、status置为idle,并取消所有进行中的请求

四种status状态

  • idle:请求尚未开始(例如设置了{ immediate: false },或在 SSR 阶段设置了{ server: false })
  • pending:请求进行中
  • success:请求成功完成
  • error:请求失败

在实际 SSR + 懒加载的首屏场景中,idle一般不会被观测到,因为 SSR 会先等待请求结束再输出 HTML;客户端导航场景下状态流转则为pending → success | error。

用watch观测渐进更新的数据

因为懒加载下posts一开始可能是undefined,你无法在await之后立即访问其内容,但可以通过watch观测它的变化。这是文档推荐的标准模式:

<script setup lang="ts"> /* useLazyFetch lets navigation continue before the fetch completes. * Handle loading and error states in the template. */ const { status, data: posts } = await useLazyFetch('/api/posts') watch(posts, (newPosts) => { // Because posts might start out null, you won't have access // to its contents immediately, but you can watch it. }) </script> <template> <div v-if="status === 'pending'"> Loading ... </div> <div v-else-if="status === 'error'"> Error loading posts </div> <div v-else> <div v-for="post in posts"> <!-- do something --> </div> </div> </template>

实战组合:动态 URL、延迟请求与手动刷新

useLazyFetch支持响应式 URL 与响应式选项,常见于列表筛选、按 ID 查询等动态请求场景。

响应式查询参数:自动重新请求

const id = ref(null) const { data, status } = useLazyFetch('/api/user', { query: { user_id: id, }, })

id变化时 Nuxt 会自动用新查询参数重新请求。该响应式能力的底层实现见 fetch.ts 的_fetchOptions:所有 fetch 选项被放入一个reactive对象,并作为 watch 源注入useAsyncData的watch数组,因此选项一旦变化即触发带cause: 'watch'的自动刷新。

computed getter URL:复杂路径构建

<script setup lang="ts"> const id = ref(null) const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, { immediate: false, }) </script> <template> <div> <!-- disable the input while fetching --> <input v-model="id" type="number" :disabled="status === 'pending'" > <div v-if="status === 'idle'"> Type a user ID </div> <div v-else-if="status === 'pending'"> Loading ... </div> <div v-else> {{ data }} </div> </div> </template>

immediate: false+execute():等待用户交互再取数

<script setup lang="ts"> const { data, error, execute, status } = await useLazyFetch('/api/comments', { immediate: false, }) </script> <template> <div v-if="status === 'idle'"> <button @click="execute"> Get data </button> </div> <div v-else-if="status === 'pending'"> Loading comments... </div> <div v-else> {{ data }} </div> </template>

上述三个组合场景均来自入门指南数据获取中关于 lazy / computed URL / not-immediate 的完整示例,可直接复制运行。

底层原理:从源码看懒加载如何实现

理解useLazyFetch的关键在于lazy: true在useAsyncData中如何改变行为。核心的分支逻辑位于 asyncData.ts 的客户端分支,其判断顺序可以概括为:

  1. SSR 或服务器端(import.meta.server且fetchOnServer && opts.immediate):通过onServerPrefetch(() => promise)注册请求,Nuxt 会等待该 promise 完成后再序列化页面——因此懒加载不改变 SSR 的 HTML 完整度;
  2. 客户端首屏 / 导航:
    • 若正在 hydration 且已有服务器数据,直接沿用,不再发起请求;
    • 若处于组件 setup 且满足opts.lazy条件,则把initialFetch压入_nuxtOnBeforeMountCbs数组,推迟到组件onBeforeMount(即将挂载)时才真正执行请求——这正是"不阻塞导航、挂载后再取数"的机制来源;
    • 否则(非 lazy)立即执行initialFetch(),从而阻塞导航。

由此可见:useFetch与useLazyFetch的分水岭,本质上是 asyncData.ts 中这段"请求何时发起"的分派逻辑——lazy 把请求从"导航前 await"推迟到"组件挂载前注册",导航因此不再等待。

另一个值得注意的实现细节:useAsyncData的execute内建了去重与取消机制。当同一 key 已有进行中的请求时,dedupe: 'cancel'(默认值)会通过AbortController中止旧请求;调用clear()同样会触发 abort,仓库测试 use-fetch.test.ts 通过 mockAbortController验证了useLazyFetch的clear会取消进行中的/api/sleep请求。

与 SSR payload 的关系

useLazyFetch(作为useFetch家族的成员)会自动把响应数据加入 Nuxt payload,使数据能够从服务器端传递到客户端,避免 hydration 时重复请求。在服务端渲染 + hydration 场景下,客户端若发现 payload 中已存在该 key 的数据,将直接使用而不再发起请求。

在获取数据前若要跨组件共享同一份数据,可以通过useNuxtData取回用useFetch/useLazyFetch(指定相同key)创建的 keyed state。若需要自定义默认选项(如baseURL或认证头)的useFetch变体,官方推荐使用createUseFetch——useLazyFetch本身就是该工厂能力的最好例证。

重要警告与注意事项

围绕useLazyFetch有以下三条必须遵守的边界:

  1. await不等待数据(客户端):在客户端导航期间await useLazyFetch(...)会立即 resolve,data仍处于默认值。必须检查模板中的status === 'pending'与status === 'error'后再使用结果;若希望导航真正等待数据,请改用useFetch。

  2. 保留函数名,不可自定义:useLazyFetch与useFetch一样属于编译器会转换的保留函数名,因此你不应把自己的函数命名为useLazyFetch。要创建带预置选项的自定义变体,应使用createUseFetch或createUseAsyncData。这在源码中亦有体现:_functionName等内部元数据正是由编译期注入,用于开发诊断。

  3. 不要引入来自其他库的同名导入:若data解构出来是字符串而非 JSON 解析对象,请检查组件中是否意外引入了import { useFetch } from '@vueuse/core'之类的冲突导入。

相关资源

  • useFetch API 参考:完整的 options 表格、类型定义与返回值说明
  • useAsyncData API 参考:更底层的异步数据原语
  • 数据获取入门指南:lazy、SSR、payload、headers 传递等系统讲解
  • createUseFetch API 参考:自定义带默认选项的 useFetch 变体
  • useLazyFetch 源码实现:工厂实例定义
  • useAsyncData 核心实现:懒加载分派、去重、abort、payload 序列化逻辑
  • 仓库单元测试:useLazyFetch的默认值、abort 行为等可执行验证

【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询