Nuxt 中 `useFetch` 完全指南:SSR 友好的数据请求组合式函数从参数到原理
Nuxt 中useFetch完全指南SSR 友好的数据请求组合式函数从参数到原理【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt本篇文章以 Nuxt 官方 API 文档 docs/4.api/2.composables/use-fetch.md 为骨架结合仓库内真实实现 packages/nuxt/src/app/composables/fetch.ts 与 packages/nuxt/src/app/composables/asyncData.ts系统讲解useFetch的用法、全部参数语义、返回值、响应式行为与内部实现原理。读完你不仅能正确写出可用的useFetch代码还能理解 key 生成、SSR 请求转发、hydration 数据复用、状态去重等底层机制避免在服务端渲染与客户端导航场景中踩坑。概述useFetch是什么useFetch是 Nuxt 提供的一个 SSR服务端渲染友好的数据请求组合式函数composable。它本质上是对两个底层 API 的封装useAsyncData负责「SSR 友好」这部分——管理异步数据的状态机、自动生成请求 key、把响应写入 Nuxt payload从而在服务端渲染完成后把数据传给客户端页面水合hydrate时不会在客户端重复请求同一份数据$fetch负责「发请求」这部分——基于 unjs/ofetch 的增强版 fetch自动处理 JSON 序列化、错误统一与拦截器等能力。相比单独使用二者useFetch额外带来了三个开箱即用的能力见 fetch.ts 中对返回值的描述自动生成请求 key根据 URL、options 以及调用点在源码中的位置计算出一个稳定且唯一的 key服务端路由类型提示当你请求server/api目录中定义的路由时TypeScript 能根据服务端路由自动推导可用的 URL 与 HTTP method自动推断 API 响应类型ResT泛型可以自动从请求中推导减少手写类型。:::noteuseFetch是一个 composable必须直接在被 Vue 组件 setup、Nuxt plugin 或路由中间件route middleware等拥有组件实例/作用域的地方调用。它返回响应式对象并将结果写入 Nuxt payload从而让数据在服务端渲染完成后被传递到客户端且客户端水合时无需重新请求。 :::文档开头将其定位为Fetch data from an API endpoint with an SSR-friendly composable——即用 SSR 友好的方式从 API 端点获取数据。在项目中的位置在源码层面packages/nuxt/src/app/composables/fetch.ts 中真正被导出的是createUseFetch工厂与默认实例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, _functionName: useLazyFetch, })从源码可以看到useLazyFetch其实只是createUseFetch({ lazy: true })的产物其完整用法见useLazyFetch文档。基本用法在页面/组件script setup中直接调用即可script setup langts const { data, status, error, refresh, clear } await useFetch(/api/modules, { pick: [title], }) /script是否需要awaituseFetch返回一个 Promise因此可以await也可以不await。两种写法的差异在于调用之后的行为await时执行会暂停直到data被填充在客户端导航时会阻塞导航直到数据就绪script setup中拿到的data一定是有值的不await时执行立即继续data先保持默认值通常是undefined直到请求 resolve 后才被填充在客户端导航时不阻塞你需要自己依据返回的status与errorref 处理加载与错误态。这与lazy选项产生的效果类似但lazy才是显式选择非阻塞导航的正式方式。在 SSR 场景中无论你是否awaitNuxt 都会等待请求完成后再渲染页面因此返回的 HTML 始终包含数据。返回值都是什么形态注意data、status、error是 Vue 的ref在script setup中需要以.value访问而refresh/execute与clear是普通函数直接调用即可。添加 query 查询参数通过query选项可以为请求添加搜索参数。该选项扩展自 unjs/ofetch内部用 unjs/ufo 构造 URL传入的ref对象会被自动解包stringifyconst param1 ref(value1) const { data, status, error, refresh } await useFetch(/api/modules, { query: { param1, param2: value2 }, })以上示例最终请求为https://api.nuxt.com/modules?param1value1param2value2。使用拦截器interceptorsuseFetch透传了 ofetch 的拦截器能力可以用来设置请求头、处理请求/响应错误、保存 token 等const { data, status, error, refresh, clear } await useFetch(/api/auth/login, { onRequest ({ request, options }) { // 设置请求头 // 注意此处依赖 ofetch 1.4.0必要时需要刷新 lockfile options.headers.set(Authorization, ...) }, onRequestError ({ request, options, error }) { // 处理请求错误 }, onResponse ({ request, response, options }) { // 处理响应数据 localStorage.setItem(token, response._data.token) }, onResponseError ({ request, response, options }) { // 处理响应错误 }, })响应式 URL 与 key、共享状态的语义响应式 URL路由变化时自动重新请求useFetch的第一个参数支持传入字符串、Request对象、Vueref或返回前两者的函数。当 URL 是响应式的并发生变化时请求会自动重新发起script setup langts const route useRoute() const id computed(() route.params.id) // 当路由变化、id 更新时数据会自动重新拉取 const { data: post } await useFetch(() /api/posts/${id.value}) /scriptkey 与共享状态自动生成的 key对每个调用点call site都是唯一的。因此在不同组件中用相同 URL 与 options 调用useFetch彼此不会共享状态会各自发起请求而同一个组件的多个实例因为使用同一调用点天然共享状态。如果你希望跨组件共享同一份data、error、status需要为每次调用显式传入相同的key::code-groupscript setup langts // 与 ComponentB 共享数据——只发起一次请求 const { data } await useFetch(/api/random, { key: random }) /scriptscript setup langts // 与 ComponentA 共享数据——只发起一次请求 const { data } await useFetch(/api/random, { key: random }) /script:::::tip 使用useFetch创建的带 key 状态可以在整个 Nuxt 应用内通过useNuxtData获取例如在另一个组件或 composable 中读取已有数据或执行手动刷新。 :::两个重要警告:::warninguseFetch是被编译器转换的保留函数名关于这一点见后文「从源码看 key 生成与保留函数名」因此不要自己定义名为useFetch的函数。需要自定义带默认选项的变体时请改用createUseFetch参考 自定义 useFetch 配方。 ::::::warning 如果你发现从useFetch解构出的data是字符串而不是 JSON 解析后的对象请检查组件里是否引入了类似import { useFetch } from vueuse/core的导入语句——它会把编译器变换指向另一个库的实现导致类型与行为都不正确。 :::响应式请求选项Reactive Fetch Options除了 URL请求选项本身也支持响应式可以传computed、ref或计算属性 getter。当某个响应式选项被更新时useFetch会以更新后的值自动重新发起请求const searchQuery ref(initial) const { data } await useFetch(/api/search, { query: { q: searchQuery }, }) // 触发重新请求/api/search?qnew%20search searchQuery.value new search若想关闭这种自动重请求将watch设为falseconst searchQuery ref(initial) const { data } await useFetch(/api/search, { query: { q: searchQuery }, watch: false, }) // 不会触发重新请求 searchQuery.value new search从实现看asyncData.tswatch: false会让响应式选项对象_fetchOptions从自动 watch 的 sources 中移除但仍保留 key 的响应式。类型签名Signature文档中给出的公开类型签名如下保持与 fetch.ts 中公开的重载一致export function useFetchResT, ErrorT NuxtErrorunknown, DataT ResT ( url: string | Request | Refstring | Request | (() string | Request), options?: UseFetchOptionsResT, DataT, ): AsyncDataDataT, ErrorT PromiseAsyncDataDataT, ErrorT type UseFetchOptionsResT, DataT ResT { key?: MaybeRefOrGetterstring method?: MaybeRefOrGetterstring query?: MaybeRefOrGetterSearchParams params?: MaybeRefOrGetterSearchParams body?: MaybeRefOrGetterRequestInit[body] | Recordstring, any headers?: MaybeRefOrGetterRecordstring, string | [key: string, value: string][] | Headers baseURL?: MaybeRefOrGetterstring cache?: false | default | force-cache | no-cache | no-store | only-if-cached | reload server?: boolean lazy?: boolean immediate?: boolean getCachedData?: (key: string, nuxtApp: NuxtApp, ctx: AsyncDataRequestContext) DataT | undefined deep?: boolean dedupe?: cancel | defer timeout?: number enabled?: MaybeRefOrGetterboolean serialize?: boolean default?: () DataT | RefDataT transform?: (input: ResT) DataT | PromiseDataT pick?: string[] $fetch?: typeof globalThis.$fetch watch?: MultiWatchSources | false } type AsyncDataRequestContext { /** The reason for this data request */ cause: initial | refresh:manual | refresh:hook | watch } type AsyncDataDataT, ErrorT { data: RefDataT | undefined pending: Refboolean refresh: (opts?: AsyncDataExecuteOptions) Promisevoid execute: (opts?: AsyncDataExecuteOptions) Promisevoid clear: () void error: RefErrorT | undefined status: RefAsyncDataRequestStatus } interface AsyncDataExecuteOptions { dedupe?: cancel | defer timeout?: number signal?: AbortSignal } type AsyncDataRequestStatus idle | pending | success | error关于AsyncDataDataT, ErrorT PromiseAsyncDataDataT, ErrorT这个联合返回类型它既是对象又是 Promise。源码中对应为asyncData.tsexport type AsyncDataData, Error _AsyncDataData, Error Promise_AsyncDataData, Error这解释了为什么既能await useFetch(...)又能直接解构出data、refresh等属性。参数详解第一参数URL类型为string | Request | Refstring | Request | (() string | Request)可以是字符串、Request对象、Vue ref或返回字符串/Request的函数完整支持动态端点的响应式。第二参数options为对象扩展了 unjs/ofetch 的 options 与AsyncDataOptions。所有选项既可以是静态值也可以是ref或 computed 值。OptionTypeDefaultDescriptionkeyMaybeRefOrGetterstringauto-gen用于去重的唯一 key。若未提供则由 URL、options 与源码中调用点位置生成。methodMaybeRefOrGetterstringGETHTTP 请求方法。queryMaybeRefOrGetterSearchParams-追加到 URL 的查询/搜索参数。别名params。paramsMaybeRefOrGetterSearchParams-query的别名。bodyMaybeRefOrGetterRequestInit[body] \| Recordstring, any-请求体。对象会被自动字符串化。headersMaybeRefOrGetterRecordstring, string \| [key, value][] \| Headers-请求头。baseURLMaybeRefOrGetterstring-请求的基础 URL。cachefalse \| string-缓存控制。布尔值false关闭缓存或使用 Fetch API 值如default、no-store等。serverbooleantrue是否在服务端请求。lazybooleanfalse为true时在路由加载完成后才 resolve不阻塞导航。immediatebooleantrue为false时阻止请求立即发起。default() DataT-在异步 resolve 之前为data提供默认值的工厂函数。timeoutnumber-请求超时毫秒数默认undefined即不超时。transform(input: DataT) DataT \| PromiseDataT-在结果 resolve 后转换结果的函数。getCachedData(key, nuxtApp, ctx) DataT \| undefined-返回缓存数据的函数默认实现见下文。pickstring[]-只从结果中挑选指定 key。watchMultiWatchSources \| false-要监听并自动刷新的响应式源数组false关闭监听。deepbooleanfalse是否用深响应 ref 返回数据。默认false浅响应 ref性能更好。dedupecancel \| defercancel避免同一 key 同时发起多次请求。enabledbooleantrue控制请求是否允许执行的「闸门」。为false时初始请求、execute/refresh、watch 触发的执行全被阻塞true → false会取消进行中的请求但不清空data重新启用不会自动重新请求。serializebooleantrue是否把 resolve 后的数据写入 Nuxt payload__NUXT_DATA__。为false时服务端取回的数据不进入 payload水合后若组件渲染了它客户端会重新请求。可配合惰性水合lazy hydration避免水合不一致与多余客户端请求。$fetchtypeof globalThis.$fetch-自定义 $fetch 实现参见 Nuxt 自定义 useFetch。:::note 所有 fetch 选项都可以传入computed或ref值。它们会被监听一旦值更新就自动用新值发起新请求除非watch设为false。 :::getCachedData默认实现const getDefaultCachedData (key, nuxtApp, ctx) nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]该默认实现只有在nuxt.config中开启了experimental.payloadExtraction时才会缓存数据。返回值详解useFetch返回一个可被 await 的 Promise——因此await后可以直接在script setup中使用data此时必有值而非undefined。你也可以不去 await 而直接解构此时在请求完成前data可能为undefined。:::tip 即便你不 await 返回值在 SSR 期间 Nuxt 也会等待请求完成并把 resolve 后的数据发送给客户端。 ::::::note 如果服务端没有请求数据例如设置了server: false那么在水合完成之前不会发起请求。这意味着即使在客户端await useFetchscript setup里的data仍然可能是undefined。 :::NameTypeDescriptiondataRefDataT \| undefined异步请求的结果。refresh(opts?: AsyncDataExecuteOptions) Promisevoid手动刷新数据的方法。默认情况下 Nuxt 会等上一次refresh完成才允许下一次执行。execute(opts?: AsyncDataExecuteOptions) Promisevoidrefresh的别名。errorRefErrorT \| undefined请求失败时的错误对象。statusRefidle \| pending \| success \| error数据请求的状态用于区分四种状态。pendingRefboolean请求进行中为true。配合experimental.pendingWhenIdle在status为idle且无缓存数据时也为true。clear() void重置data为undefined若提供了options.default()则为该值、error为undefined、status设为idle并取消任何进行中的请求。:::tip 如果你没有 await 返回值Promise 的then、catch、finally也可以被安全地解构出来使用。 :::四种状态取值Status Valuesidle请求尚未开始例如{ immediate: false }或在服务端渲染时{ server: false }pending请求进行中success请求成功完成error请求失败。结合源码理解内部实现从createUseFetch到useFetch在 fetch.ts 中createUseFetch通过defineKeyedFunctionFactory构造内部返回的useFetch函数完成三个核心步骤合并默认选项工厂选项options与调用方选项opts按「普通对象工厂 用户函数模式用户 工厂」的策略合并随后把server、lazy、default、transform、pick、watch、immediate、getCachedData、deep、dedupe、timeout、enabled、serialize等 AsyncData 层选项单独拆出其余全部归入fetchOptions传给$fetch见 fetch.ts构造响应式 URL 与 key把真正的请求包进useAsyncData的 handler。自动 key 是怎么生成的关键在 fetch.tsconst _request computed(() toValue(request)) const key computed(() toValue(fetchOptions.key) || ($f hashKey([autoKey, typeof _request.value string ? _request.value : , ...generateOptionSegments(fetchOptions)])))前缀$f区分 fetch 类型的 keyautoKey是编译器在调用点注入的源码位置信息所以同一调用点的多次实例能共享 key不同组件不同调用点则不会共享generateOptionSegmentsfetch.ts会把method、baseURL、query/params、body解包成可哈希片段——例如 body 为ArrayBuffer、FormData或普通对象时分别以不同方式序列化后参与 key 计算保证不同请求参数对应不同 key。这也解释了前文警告useFetch是编译器要识别的保留函数名编译器依赖源码调用位置来生成autoKey因此你不应自造同名函数。另外值得注意若传入的 URL 以//开头协议相对地址且未提供baseURL会直接抛出错误NUXT_E3001见 fetch.tsNuxt 要求请求使用完整协议。SSR 阶段用useRequestFetch转发真正发起请求的 handler 位于 fetch.tsconst asyncData useAsyncData_ResT, ErrorT, DataT, PickKeys, DefaultT(key, (_, { signal }) { const _$fetch: TypedFetchunknown, TypedFetchRequest fetchOptions.$fetch || (import.meta.server ? useRequestFetch() : $fetch) ... return _$fetch(_request.value, resolvedOptions as any) as Promise_ResT }, _asyncDataOptions)当运行在服务端时Nuxt 使用useRequestFetch()见useRequestFetch作为请求实例其实现会继承当前请求上下文中的 headers/cookies让服务端内部请求携带与 SSR 首屏请求一致的会话信息useFetch文档中那句「基于 server routes 提供请求 URL 的类型提示」即来自TypedFetchRequest/TypedServerResponse类型types/fetch 相关类型经#build/fetch注入。SSR 到 Client 的生命周期请求、payload、水合asyncData.ts 中可以看到完整的数据生命周期逻辑服务端asyncData.ts若fetchOnServer且immediate立即执行初始请求并把它注册到onServerPrefetch组件内或app:createdhook确保页面渲染前数据已就绪水合期asyncData.ts若服务端已有数据key存在于nuxtApp.payload.data或getCachedData命中则直接复用不再发起新请求——这正是「不重复请求」的实现基础客户端导航 / 组件挂载asyncData.ts根据server: false、lazy: true、immediate等标志决定是在onBeforeMount后取数还是同步阻塞等待。值得留意的默认值归位逻辑asyncData.tsserver默认true、lazy默认false、immediate默认true、dedupe默认cancel、deep默认取自asyncDataDefaults——它们与上文参数表一一对应构成了useFetch的默认行为。去重dedupe与共享状态容器所有带 key 的 async data 实体都存放在nuxtApp._asyncData[key]中不同组件通过同一个 key 读取同一个实体从而共享状态、避免重复请求dedupe: cancel时若同一 key 已有请求在途新触发会取消/等待合并。这也是useNuxtData能拿到同一份数据、refreshNuxtData能统一刷新的原因。进阶用createUseFetch自定义默认行为如果你需要一个带默认值如baseURL、认证 headers、统一的transform的自定义useFetch官方推荐使用createUseFetch自 v4.2 引入源码见 fetch.ts。它有两种传参模式传普通对象作为默认值调用方的选项可以覆盖工厂默认值defaults 模式传函数工厂返回值会覆盖调用方的选项override 模式适合强制某些行为。完整的类型安全示例见 自定义 useFetch 配方 与createUseFetch文档。进一步阅读服务端数据获取入门指南数据获取的完整场景演练useAsyncData参考文档理解useFetch底层的 AsyncData 状态机useLazyFetch与useNuxtData非阻塞取数与跨组件共享/读取$fetch了解底层请求工具全部能力useRequestFetch服务端请求上下文转发机制实现源码packages/nuxt/src/app/composables/fetch.ts 与 packages/nuxt/src/app/composables/asyncData.ts。【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

日新闻

周新闻

月新闻