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

Apollo Client的InMemoryCache merge函数仅返回ref属性如何处理

问题原因

Apollo Client 的 InMemoryCache 会默认对所有携带__typename和id/_id字段的对象做规范化存储:所有实体对象会单独存在缓存根节点,对应查询字段下仅存储指向实体的ref引用,所以你在merge函数中直接打印incoming拿到的就是ref数组而非原始数据,这是正常的缓存机制。

解决方案

方案1:保留规范化,直接合并ref数组(推荐)

你不需要在merge函数中手动解析ref,Apollo Client 会在将数据返回给useQuery前自动把所有ref解析为完整的实体数据,仅需补充两个配置即可实现分页列表合并:

  1. 配置keyArgs排除分页参数,确保不同页的查询命中同一个缓存字段
  2. 给existing加默认值避免首次查询解构报错
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';

const httpLink = createHttpLink({
  uri: '你的GraphQL接口地址'
});

const client = new ApolloClient({
  ssrMode: typeof window === 'undefined',
  link: httpLink,
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          todos: {
            // 如果你的查询有非分页的过滤参数需要作为缓存键,就写成数组,比如['filter', 'status'],把page、limit等分页参数排除即可
            keyArgs: false,
            merge(existing = [], incoming) {
              // 直接合并ref数组即可,组件拿到的会是完整的实际数据
              return [...existing, ...incoming];
            }
          }
        }
      }
    }
  })
});

方案2:在merge函数中读取实际数据做业务处理

如果需要在merge阶段对数据做去重、排序等自定义操作,可以用merge函数上下文提供的工具方法解析ref:

todos: {
  keyArgs: false,
  merge(existing = [], incoming, { readField, isReference }) {
    // 解析ref为实际数据的工具函数
    const getRealItem = (item) => {
      if (isReference(item)) {
        return {
          id: readField('id', item),
          content: readField('content', item),
          completed: readField('completed', item),
          // 补充你需要的其他Todo字段
        }
      }
      return item;
    }

    const existingList = existing.map(getRealItem);
    const incomingList = incoming.map(getRealItem);

    // 示例:合并时按id去重
    const uniqueIds = new Set(existingList.map(item => item.id));
    const newItems = incomingList.filter(item => !uniqueIds.has(item.id));
    
    return [...existingList, ...newItems];
  }
}

方案3:关闭Todo类型规范化,直接存储原始数据

如果你的Todo列表不需要单独缓存实体,可以直接关闭该类型的规范化,merge函数中拿到的就是原始数据:

const cache = new InMemoryCache({
  typePolicies: {
    Todo: {
      // 关闭该类型的规范化存储
      keyFields: false
    },
    Query: {
      fields: {
        todos: {
          keyArgs: false,
          merge(existing = [], incoming) {
            return [...existing, ...incoming]
          }
        }
      }
    }
  }
})

注意事项

  • 如果使用偏移分页或者Relay风格的游标分页,可以直接使用@apollo/client/utilities提供的offsetLimitPagination、relayStylePagination工具函数,无需手动编写merge逻辑。
  • 必须正确配置keyArgs,如果分页参数被包含在keyArgs中,不同页的查询会被识别为独立缓存字段,不会触发merge逻辑。

内容的提问来源于stack exchange,提问作者Roosevelt Jointer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:57:02