You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:20:06