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

相关推荐
xcfox7 天前
Nexus 评估报告
typescript·graphql
怀川8 天前
开源 NamBlog:一个博客外壳下的体验编译器
docker·ai·.net·博客·ddd·graphql·mcp
马达加斯加D8 天前
Web系统设计 --- HTTP + GraphQL
前端·http·graphql
自己的九又四分之三站台10 天前
PG GraphQL详细介绍与基本使用
数据库·sql·graphql
自己的九又四分之三站台12 天前
导入数据到OG GraphQL以及创建graph
java·后端·graphql
咨询qq 87622396525 天前
一种增强的独立分量分析方法-基于小波域(MATLAB R2018B) 压缩包=数据+代码+参考...
graphql
Tadas-Gao1 个月前
GraphQL:下一代API架构的设计哲学与实践创新
java·分布式·后端·微服务·架构·graphql
Sui_Network1 个月前
备受期待的 POP 射击游戏 XOCIETY 正式在 Epic Games Store 开启体验
人工智能·游戏·rpc·区块链·量子计算·graphql
闲人编程1 个月前
GraphQL与REST API对比与实践
后端·python·api·graphql·rest·codecapsule
闲人编程1 个月前
Django与GraphQL:使用Graphene构建现代化API
django·sqlite·graphql·codecapsule·graphene