背景 帮看nuxt的一个项目,nuxt 在 onMounted 的时候, 使用 useFetch 没有请求发出的问题
最终我调试追踪到因为每次key相同,导致直接使用缓存,使得请求不会发出。 那么我们看一下key是如何生成的呢? 使用 ohash 进行生成。
hash([autoKey, typeof _request.value === 'string' ? _request.value : '', ...generateOptionSegments(opts)])
arg1 在此是一个对象,如文档所述。 得 hash([undefined, '', ...generateOptionSegments(opts)])
function generateOptionSegments <_ResT, DataT, DefaultT>(opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) {
const segments: Array<string | undefined | Record<string, string>> = [
toValue(opts.method as MaybeRef<string | undefined> | undefined)?.toUpperCase() || 'GET',
toValue(opts.baseURL),
]
for (const _obj of [opts.params || opts.query]) {
const obj = toValue(_obj)
if (!obj) { continue }
const unwrapped: Record<string, string> = {}
for (const [key, value] of Object.entries(obj)) {
unwrapped[toValue(key)] = toValue(value)
}
segments.push(unwrapped)
}
return segments
}
这里使用 method baseURL params query 进行计算。如果 method baseURL params query 一直一样,那么 key 也会是一样的, 会忽略 body, 所以缓存依然使用。
const _fetchOptions = reactive({
...fetchDefaults,
...fetchOptions,
cache: typeof opts.cache === 'boolean' ? undefined : opts.cache
})
有意思的是, 文档里面没有标注 cache 字段的使用; https://nuxt.com/docs/api/composables/use-fetch
useFetch 源码如下:
export function useFetch<
ResT = void,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
Method extends AvailableRouterMethod<ReqT> = ResT extends void ? 'get' extends AvailableRouterMethod<ReqT> ? 'get' : AvailableRouterMethod<ReqT> : AvailableRouterMethod<ReqT>,
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
DataT = _ResT,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = null,
> (
request: Ref<ReqT> | ReqT | (() => ReqT),
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
arg2?: string
) {
const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
const _request = computed(() => {
let r = request
if (typeof r === 'function') {
r = r()
}
return toValue(r)
})
const _key = opts.key || hash([autoKey, typeof _request.value === 'string' ? _request.value : '', ...generateOptionSegments(opts)])
if (!_key || typeof _key !== 'string') {
throw new TypeError('[nuxt] [useFetch] key must be a string: ' + _key)
}
if (!request) {
throw new Error('[nuxt] [useFetch] request is missing.')
}
const key = _key === autoKey ? '$f' + _key : _key
if (!opts.baseURL && typeof _request.value === 'string' && (_request.value[0] === '/' && _request.value[1] === '/')) {
throw new Error('[nuxt] [useFetch] the request URL must not start with "//".')
}
const {
server,
lazy,
default: defaultFn,
transform,
pick,
watch,
immediate,
getCachedData,
deep,
...fetchOptions
} = opts
const _fetchOptions = reactive({
...fetchDefaults,
...fetchOptions,
cache: typeof opts.cache === 'boolean' ? undefined : opts.cache
})
const _asyncDataOptions: AsyncDataOptions<_ResT, DataT, PickKeys, DefaultT> = {
server,
lazy,
default: defaultFn,
transform,
pick,
immediate,
getCachedData,
deep,
watch: watch === false ? [] : [_fetchOptions, _request, ...(watch || [])]
}
let controller: AbortController
const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys, DefaultT>(key, () => {
controller?.abort?.()
controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController
/**
* Workaround for `timeout` not working due to custom abort controller
* TODO: remove this when upstream issue is resolved
* @see https://github.com/unjs/ofetch/issues/326
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
*/
const timeoutLength = toValue(opts.timeout)
if (timeoutLength) {
setTimeout(() => controller.abort(), timeoutLength)
}
let _$fetch = opts.$fetch || globalThis.$fetch
// Use fetch with request context and headers for server direct API calls
if (import.meta.server && !opts.$fetch) {
const isLocalFetch = typeof _request.value === 'string' && _request.value[0] === '/' && (!toValue(opts.baseURL) || toValue(opts.baseURL)![0] === '/')
if (isLocalFetch) {
_$fetch = useRequestFetch()
}
}
return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any) as Promise<_ResT>
}, _asyncDataOptions)
return asyncData
}