Nuxt3 useFetch缓存问题 您所在的位置:网站首页 表单post请求被缓存 Nuxt3 useFetch缓存问题

Nuxt3 useFetch缓存问题

2024-03-05 18:48| 来源: 网络整理| 查看: 265

最近在使用 Nuxt3 提供的 useFetch 方法时,第一次请求可以正常收发,之后同一个请求就不发送了,所以看了一下 useFetch 的实现,发现是在特定场景下,命中了 nuxt 接口数据缓存。

正常情况:

1.gif

刷新页面后,命中缓存:

2.gif

先看下 useFetch 的 API:

function useFetch( url: string | Request | Ref | () => string | Request, options?: UseFetchOptions ): Promise type UseFetchOptions = { key?: string method?: string query?: SearchParams params?: SearchParams body?: RequestInit['body'] | Record headers?: Record | [key: string, value: string][] | Headers baseURL?: string server?: boolean lazy?: boolean immediate?: boolean default?: () => DataT transform?: (input: DataT) => DataT pick?: string[] watch?: WatchSource[] } type AsyncData = { data: Ref pending: Ref refresh: (opts?: { dedupe?: boolean }) => Promise execute: () => Promise error: Ref }

useFetch 是对 useAsyncData 和 $fetch 的封装,默认情况下,如果 url 或者 options 是响应式的数据,会自动触发请求,更新请求结果。 这会带来一些方便,比如常见的列表搜索场景,只需将筛选条件变成响应式数据,作为 options 调用 useFetch 即可。 不过,有些场景下可能不适用,比如需要组合几个筛选条件再去查询,就可能在每个条件变化的时候多触发了几次查询请求。 useFetch 还可以通过 options 的 server 字段方便的实现 SSR。

接口数据缓存

问题出现: 商品加购请求 /cart/{id} 支持重复发送,测试过程中发现,第一次发送加购请求,收到响应之后,后续同一商品加购请求不会再次发出了。单步调试发现,命中了nuxt内部的接口数据缓存。 useFetch 显示调用以及监听到 url、options 变化,都是调用 refresh方法,只是_initial参数不同。

简化后的源代码:

asyncData.refresh = asyncData.execute = (opts = {}) => { // Avoid fetching same key that is already fetched if (opts._initial && hasCachedData()) { return getCachedData() } asyncData.pending.value = true const promise = new Promise( (resolve, reject) => { try { resolve(handler(nuxt)) } catch (err) { reject(err) } }) .then((_result) => { asyncData.data.value = result asyncData.error.value = null }) .catch((error: any) => { asyncData.error.value = error asyncData.data.value = unref(options.default?.() ?? null) }) .finally(() => { asyncData.pending.value = false nuxt.payload.data[key] = asyncData.data.value } }) nuxt._asyncDataPromises[key] = promise return nuxt._asyncDataPromises[key] }

可以看到,接口请求之后,在 finally 中更新了 nuxt.payload.data,

nuxt.payload.data[key] = asyncData.data.value

下一次请求,首先判断 nuxt.payload.data 有没有缓存,有缓存不再重复请求

if (opts._initial && hasCachedData()) { return getCachedData() } const getCachedData = () => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key] 命中缓存的条件 1. opts._initial === true client端主动触发的请求 _initial 的值都是 ture watch监听到依赖变化和 app:data:refresh hook 触发的请求 _initial 为 false,不会命中缓存 2. nuxt.isHydrating === true

创建nuxt实例:

function createNuxtApp (options: CreateOptions) { let hydratingCount = 0 const nuxtApp: NuxtApp = { isHydrating: process.client, deferHydration () { if (!nuxtApp.isHydrating) { return () => {} } hydratingCount++ let called = false // deferHydration返回方法,后面统一叫做 cb return () => { if (called) { return } called = true hydratingCount-- if (hydratingCount === 0) { nuxtApp.isHydrating = false return nuxtApp.callHook('app:suspense:resolve') } } } } return nuxtApp }

client端应用初始化时,isHydrating 为 ture,调用 deferHydration 的返回方法 cb 才可以将 isHydrating 的值置为 false,有两个时机:

page:finish hook 的回调方法 nuxt-root Suspense的 resolve 回调方法 3. nuxt.payload.data[key]通过 key 查询到接口数据缓存

isHydrating 为 true 时,请求就会通过 key 去查接口数据缓存

a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the static code location where useAsyncData is used.

key如果不传的话,会默认生成一个:

function useFetch(request, arg1, arg2 ) { const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2] const _key = opts.key || hash([autoKey, unref(opts.baseURL), typeof request === 'string' ? request : '', unref(opts.params || opts.query)]) if (!_key || typeof _key !== 'string') { throw new TypeError('[nuxt] [useFetch] key must be a string: ' + _key) } const key = _key === autoKey ? '$f' + _key : _key // ... } arg2 是一个默认的 hash,也就是上面代码中的 autoKey,在 nuxt 的插件中被自动生成,实际就是通过 AST 找到 useFetch,然后根据一系列的规则,生成这样一个 hash,作为 useFetch 的第三个参数

image.png 具体的生成规则感兴趣的朋友可以自行翻阅packages/vite/src/plugins/composable-keys.ts 查看。

_key的优先级:options.key > 通过 autoKey,opts.baseURL,opts.params,opts.query 等计算出的 hash 正常流程

渲染 (即 nuxt-root ) -> 渲染 -> 执行 layout supsense 的 resolve cb -> 执行 NuxtPage 回调 cb ( Suspense 的 reslove 事件)

由于 cb 是个闭包,第一次执行时 hydratingCount === 2,执行两次之后 hydratingCount === 0,所以会将 isHydrating 置为 false,client端同构完成。后续路由发生变化,重新渲染 NuxtPage,执行 deferHydration,由于 isHydrating 为 false, cb 是一个空方法,不会再更新 isHydrating, 就不会命中缓存;

即开启服务端渲染时,server 端将接口数据同步到 nuxt.payload.data 中,client 端初次渲染时,不再重复请求;后续 update 流程才去正常请求;

异常流程 url 和 options 都不是 响应式数据,且请求都是用户手动触发的,满足 _initial === true 某一个页面作为首屏或者刷新时,在 setup 或者 watch immediate 中使用 useRouter().replace 改变了 query 参数,触发 NuxtPage 重新渲染,这时 hydratingCount 会累加;RouterView 匹配到同一个组件,Suspense resolve事件只执行一次,hydratingCount 没有减小到 0,nuxtApp.isHydrating 不会更新,还是 true options 中没有提供 key,由 nuxt 自动生成,由于 opts.baseURL,opts.params,opts.query 都没有提供,所以都是 undefined,这样每次计算出来的 _key 都是同一个;这样第一次nuxt.payload.data[key]为空,拿到响应赋值后,nuxt.payload.data[key]就会被赋值

三个条件都满足,所有就命中了 nuxt 的接口数据缓存

清除接口数据缓存

如果接口的传参不变,但是需要实时请求,可以在useFetch option 传入 key,请求响应使用后手动调用 clearNuxtData(${mykey});,将数据从 nuxt.payload.data中清除。 image.png

通过上一步,可以把当前请求的缓存清除,但是只是针对这一个请求,其他请求还是会有同样的问题,可以手动将缓存清空或者isHydrating设置为 false image.png

另一个方法是在onMounted钩子中改变路由,也可以避免缓存问题



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有