Apollo Client Cache的缓存原理

Apollo Client 的 InMemoryCache 使用的是一种 层次化的缓存策略,结合了:

  1. 基于 field + arguments 的关键词缓存(Query 层级)
  2. 基于 __typename + id 的实体归一化缓存(Entity normalization)

这是 Apollo Cache 高性能和强可拓展性的根本所在。


📦 Apollo 缓存体系 详解

✅ 第一层:Query 层作为入口 ------ fieldName + arguments 唯一标识

当你发起一个 GraphQL 查询:

graphql 复制代码
query GetBooks($category: String!) {
  books(category: $category) {
    id
    title
  }
}

假如你传了变量 { category: "fiction" },Apollo 会在缓存中以以下格式为入口:

jsonc 复制代码
ROOT_QUERY: {
  "books({\"category\":\"fiction\"})": [ // 对应的是 books 字段,带参数
    { __ref: "Book:1" },
    { __ref: "Book:2" }
  ]
}

这里的 key 是 books({"category":"fiction"}),它是由字段名 + 参数组成。


✅ 第二层:实体归一化(Normalization) ------ __typename:id 为缓存主键

当 Apollo 发现你返回的数据中包含了一个对象,并且这个对象中有 __typename + id(或者在 typePolicies 中配置的 key 字段),它会将其单独提取出来,作为唯一实体存储。

jsonc 复制代码
"Book:1": {
  id: "1",
  __typename: "Book",
  title: "The Great Gatsby"
},
"Book:2": {
  id: "2",
  __typename: "Book",
  title: "1984"
}

这样做的好处

  • 去重:一本书可能出现在多个查询里,统一存储避免数据重复。
  • 响应式更新 :当某个字段更新时(如 title),所有引用它的地方都会自动响应式更新。
  • 强大的缓存合并能力 :通过定制 typePolicies.merge(),你可以合并数据,而不是覆盖。

🛠️ 自定义 Primary Key: typePolicies.keyFields

默认情况下,Apollo 使用 __typename + id 作为唯一主键,但你可以自定义:

ts 复制代码
const cache = new InMemoryCache({
  typePolicies: {
    Book: {
      keyFields: ["isbn"]  // 用 isbn 替代 id
    },
  },
});

🤯 多层连接:如何看缓存结构?

查询嵌套字段时,如:

graphql 复制代码
{
  user {
    id
    name
    posts {
      id
      title
    }
  }
}

缓存结构如下:

jsonc 复制代码
ROOT_QUERY: {
  "user": { __ref: "User:1" }
},
"User:1": {
  id: "1",
  __typename: "User",
  name: "Alice",
  posts: [
    { __ref: "Post:1" },
    { __ref: "Post:2" }
  ]
},
"Post:1": {
  id: "1",
  __typename: "Post",
  title: "Hello World"
},
"Post:2": {
  id: "2",
  __typename: "Post",
  title: "GraphQL Rocks"
}

🧠 如果内层对象没有提供id,缓存结构又是什么样了?

bash 复制代码
graphql
{
  user {
    id
    name
    posts {
      title
    }
  }
}

若只有 title 没有 id/__typename,Apollo cache 结构将类似:

bash 复制代码
jsonc
{
  "User:1": {
    id: "1",
    __typename: "User",
    name: "Alice",
    posts: [
      {
        title: "Hello World"
      },
      {
        title: "GraphQL Rocks"
      }
    ]
  }
}
  • posts 数组被内联嵌套存储User:1 的字段中。
  • ❌ 而不是像 posts: [{ __ref: "Post:1" }] 这样使用 __ref
  • ❌ 这些 post 项不会被 Apollo 单独归一化处理。

🎯 总结

特性 描述
查询记录 Apollo 会将查询结果按 fieldName + arguments 存成一个入口(如 books({ category: "fiction" })
实体归一化 所有实体根据 __typename + id 作为主键,单独提取并保存
引用机制 Query 层只是一个数组或对象的引用(__ref
自定义主键 使用 keyFields 修改默认的主键策略
优势 响应式 UI 更新、缓存去重、高可定制性

💡 正因为如此,Apollo 的缓存才如此强大灵活。你可以根据查询字段缓存数据,也可以通过 id 快速访问或修改实体,同时还能避免冗余和状态不一致的问题。

相关推荐
大黄说说3 天前
RESTful API 与 GraphQL:架构选型指南
架构·restful·graphql
大黄说说4 天前
RESTful API vs GraphQL:设计哲学、性能博弈与选型指南
后端·restful·graphql
忙碌54424 天前
区块链应用开发的完整实战指南:从理论到落地的企业级解决方案
架构·区块链·restful·graphql
yuki_uix1 个月前
GraphQL 重塑:从 API 语言到 AI 时代的"逻辑神经系统"
前端·graphql
果粒蹬i1 个月前
【HarmonyOS】RN of HarmonyOS实战开发项目+Apollo GraphQL客户端
华为·harmonyos·graphql
程序猿阿伟1 个月前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
程序猿阿伟1 个月前
《GraphQL状态图建模与低时延控制能力解析》
后端·graphql
程序猿阿伟1 个月前
《面向第三方的GraphQL开放平台设计指南》
后端·graphql
程序猿阿伟1 个月前
《GraphQL 强类型架构下的错误处理体系设计指南》
后端·架构·graphql
爬山算法2 个月前
Hibernate(78)如何在GraphQL服务中使用Hibernate?
java·hibernate·graphql