React.cache传入对象缓存失效,请求重复触发问题求助
解决React.cache对象参数缓存失效问题
问题核心
React.cache依赖参数的引用相等性生成缓存键,当传入内容一致但引用不同的对象时,会被判定为不同缓存键,导致重复发起请求。比如你两次调用cachedGraphqlServerRequest时传入的对象,内容完全相同但每次都是新创建的对象,引用不同,因此无法命中缓存。
解决方案
方案1:拆分关键参数作为缓存键
如果请求的唯一标识由特定字段(比如你的场景中的document、variables、withAuth)决定,可将这些字段单独作为参数传入缓存函数,替代整个对象。只要关键字段值相同,即使原对象引用不同,也能命中缓存。
修改后的代码:
import { cache } from "react" // 将关键参数拆分为独立入参 export async function graphqlServerRequest<T, V extends Variables = Variables>( document: RequestDocument | TypedDocumentNode<T, V>, variables?: V, withAuth = false, requestHeaders?: GraphQLClientRequestHeaders, onError?: (error: unknown) => void ): Promise<T | undefined> { try { if (withAuth) { return await graphqlServerWithAuth.request(document, variables, requestHeaders) } else { return await graphqlServerWithoutAuth.request(document, variables, requestHeaders) } } catch (error) { if (onError) onError(error) else throw error } } export const cachedGraphqlServerRequest = cache(graphqlServerRequest)
使用示例:
// page.tsx await cachedGraphqlServerRequest( someQuery, { slug }, true, undefined, () => notFound() ) await cachedGraphqlServerRequest( someQuery, { slug }, true, undefined, () => notFound() )
方案2:序列化可缓存字段生成稳定键
若必须传递对象参数,可在缓存逻辑中剥离不可序列化的字段(比如onError函数),将剩余可序列化字段序列化为字符串作为缓存判定依据。注意需确保序列化后的字符串能唯一标识请求。
修改后的代码:
import { cache } from "react" interface GraphqlRequestParameters<T, V> { document: RequestDocument | TypedDocumentNode<T, V> variables?: V withAuth?: boolean requestHeaders?: GraphQLClientRequestHeaders onError?: (error: unknown) => void } // 生成稳定缓存键:提取可序列化字段并序列化 const createCacheKey = <T, V>(params: Omit<GraphqlRequestParameters<T, V>, 'onError'>) => { // 针对GraphQL Document,提取查询语句作为唯一标识 const docKey = params.document.loc?.source?.body || JSON.stringify(params.document) return JSON.stringify({ document: docKey, variables: params.variables, withAuth: params.withAuth, requestHeaders: params.requestHeaders }) } // 包装缓存逻辑,剥离onError字段 export const cachedGraphqlServerRequest = cache(async <T, V extends Variables = Variables>( params: GraphqlRequestParameters<T, V> ): Promise<T | undefined> => { const { onError, ...cacheableParams } = params try { return await graphqlServerRequest(cacheableParams) } catch (error) { if (onError) onError(error) else throw error } })
方案3:手动管理外部缓存层
如果React.cache的默认机制无法满足需求,可使用Map手动实现基于内容相等性的缓存管理。
示例代码:
import { cache } from "react" interface GraphqlRequestParameters<T, V> { document: RequestDocument | TypedDocumentNode<T, V> variables?: V withAuth?: boolean requestHeaders?: GraphQLClientRequestHeaders onError?: (error: unknown) => void } // 手动缓存存储,键为序列化后的请求标识 const requestCache = new Map<string, Promise<any>>() // 生成唯一缓存键 const getCacheKey = <T, V>(params: Omit<GraphqlRequestParameters<T, V>, 'onError'>) => { const docKey = params.document.loc?.source?.body || JSON.stringify(params.document) return JSON.stringify({ document: docKey, variables: params.variables, withAuth: params.withAuth, requestHeaders: params.requestHeaders }) } export async function cachedGraphqlServerRequest<T, V extends Variables = Variables>( params: GraphqlRequestParameters<T, V> ): Promise<T | undefined> { const { onError, ...cacheableParams } = params const cacheKey = getCacheKey(cacheableParams) // 命中缓存直接返回 if (requestCache.has(cacheKey)) { try { return await requestCache.get(cacheKey) } catch (error) { requestCache.delete(cacheKey) // 请求失败时清除缓存,下次重新发起 if (onError) onError(error) else throw error } } // 未命中缓存则发起请求并缓存Promise const requestPromise = graphqlServerRequest(cacheableParams) requestCache.set(cacheKey, requestPromise) try { const result = await requestPromise return result } catch (error) { requestCache.delete(cacheKey) if (onError) onError(error) else throw error } }
注意事项
- 函数类型参数(如
onError)无法被序列化,必须从缓存判定逻辑中排除,否则会导致缓存键永远不匹配。 - 对于GraphQL的
document,优先使用其对应的查询语句字符串作为标识,避免因节点对象引用不同导致缓存失效。
内容的提问来源于stack exchange,提问作者PepeW
相关产品推荐
相关产品推荐

