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 快速访问或修改实体,同时还能避免冗余和状态不一致的问题。

相关推荐
canonical_entropy1 天前
Nop入门-如何通过配置扩展服务函数的返回对象
spring·mvc·graphql
kngines23 天前
【实战ES】实战 Elasticsearch:快速上手与深度实践-7.3.2使用GraphQL封装查询接口
大数据·elasticsearch·搜索引擎·graphql
whoamiZQ2 个月前
GraphQL-Client 使用教程与常见问题处理
java·graphql
桂月二二2 个月前
使用 SurrealDB 构建高效的 GraphQL 后端
后端·graphql
大梦百万秋2 个月前
探索 GraphQL:API 设计的未来趋势
后端·graphql
老大白菜2 个月前
使用 Go 和 gqlgen 实现 GraphQL API:实战指南
开发语言·golang·graphql
老大白菜2 个月前
Egg.js GraphQL 完整指南
开发语言·javascript·graphql
老大白菜2 个月前
GraphQL 教程
后端·graphql
Мартин.2 个月前
[Meachines] [Easy] Help HelpDeskZ-SQLI+NODE.JS-GraphQL未授权访问+Kernel<4.4.0权限提升
后端·node.js·graphql