首页
/ Apollo Client 中使用 cache.modify 处理订阅数据的类型安全实践

Apollo Client 中使用 cache.modify 处理订阅数据的类型安全实践

2025-05-11 17:43:03作者:明树来

背景介绍

在现代前端开发中,GraphQL 订阅(Subscription)是实现实时功能的重要手段。Apollo Client 作为流行的 GraphQL 客户端,提供了强大的缓存机制来处理订阅数据。然而,在使用 TypeScript 开发时,如何类型安全地操作缓存成为了一个常见挑战。

核心问题

当应用程序通过订阅接收到新数据时,需要将这些数据合并到现有的缓存中。典型的场景包括:

  1. 帖子(Post)和评论(Comment)的关系
  2. 对话(Conversation)和步骤(Step)的关系

在这些关系中,父实体(如 Post 或 Conversation)通常通过连接(Connection)字段(如 comments 或 steps)关联子实体列表。当订阅推送新数据时,我们需要将这些新数据追加到缓存中的对应列表。

解决方案演进

初始方案:直接修改缓存

最初的尝试是直接使用 cache.modify 方法修改缓存:

cache.modify({
  id: cache.identify({
    __typename: "Post",
    id: comment.post.id,
  }),
  fields: {
    comments(existing) {
      return {
        ...existing,
        edges: [...existing.edges, { node: comment }],
      };
    },
  },
});

这种方法虽然能工作,但存在明显的类型安全问题:

  • existing 参数类型为 any,缺乏类型检查
  • 无法处理缓存引用(Reference)的情况
  • 忽略了连接字段的完整类型定义(如缺少 cursor 等必需字段)

改进方案:使用 readField 辅助方法

为了解决类型安全问题,引入了 readField 方法:

cache.modify<Conversation>({
  id: cache.identify({
    __typename: "Conversation",
    id: step.conversation.id,
  }),
  fields: {
    steps(existing, { readField }) {
      const edges = readField("edges", existing) || [];
      return {
        ...existing,
        edges: [...edges, { node: step }],
      };
    },
  },
});

这种方法仍然存在问题:

  • readField 返回类型过于宽泛
  • 无法确保返回的数据符合 Connection 类型的完整定义
  • 类型断言可能导致运行时错误

最终方案:结合 updateQuery 和类型保护

经过多次尝试,最终确定了一个更可靠的方案:

const onNewStep = (step: DetailedStepFragment) => {
  cache.updateQuery(
    {
      query: GQL.Conversation.StepsQuery,
      variables: { id: step.conversation.id, last: 8 },
    },
    (existing) => {
      if (existing?.node?.__typename !== "Conversation") return;
      const node = frag(GQL.Conversation.WithSteps, existing.node);
      return {
        ...existing,
        node: {
          ...node,
          steps: {
            ...node.steps,
            edges: [
              ...node.steps.edges,
              { __typename: "ConversationStepEdge", node: step },
            ],
          },
        },
      };
    }
  );
};

这个方案的优点:

  1. 通过 updateQuery 直接操作查询结果,类型更明确
  2. 使用类型保护确保操作的安全性
  3. 完整维护了连接类型的所有必需字段
  4. 与现有查询结构保持一致

最佳实践建议

  1. 优先使用 updateQuery:当需要精确控制缓存更新时,updateQuery 通常比 modify 更类型安全。

  2. 正确处理连接类型:确保在添加新边(edge)时包含所有必需字段,如 __typenamecursor

  3. 考虑分页策略:如果使用了 relayStylePagination,需要注意手动更新可能会绕过分页策略。

  4. 使用最新 API:Apollo Client 3.11+ 提供了改进的 useSubscription hook,支持 ignoreResults 选项,可以更好地控制组件重渲染。

  5. 避免 useEffect 中的订阅处理:推荐使用 onData 回调来处理订阅数据,而不是依赖 useEffect

总结

在 Apollo Client 中处理订阅数据时,类型安全是关键考量。通过合理选择缓存操作方法(modifyupdateQuery)并结合 TypeScript 的类型保护,可以构建既安全又高效的实时数据更新机制。开发者应当根据具体场景选择最适合的方法,同时遵循 Apollo Client 的最佳实践。

登录后查看全文
热门项目推荐

项目优选

收起
RuoYi-Vue3RuoYi-Vue3
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
854
505
kernelkernel
deepin linux kernel
C
21
5
openHiTLSopenHiTLS
旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!
C
246
288
ShopXO开源商城ShopXO开源商城
🔥🔥🔥ShopXO企业级免费开源商城系统,可视化DIY拖拽装修、包含PC、H5、多端小程序(微信+支付宝+百度+头条&抖音+QQ+快手)、APP、多仓库、多商户、多门店、IM客服、进销存,遵循MIT开源协议发布、基于ThinkPHP8框架研发
JavaScript
93
15
UAVSUAVS
智能无人机路径规划仿真系统是一个具有操作控制精细、平台整合性强、全方向模型建立与应用自动化特点的软件。它以A、B两国在C区开展无人机战争为背景,该系统的核心功能是通过仿真平台规划无人机航线,并进行验证输出,数据可导入真实无人机,使其按照规定路线精准抵达战场任一位置,支持多人多设备编队联合行动。
JavaScript
78
55
nop-entropynop-entropy
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
7
0
vue-devuivue-devui
基于全新 DevUI Design 设计体系的 Vue3 组件库,面向研发工具的开源前端解决方案。
TypeScript
615
74
ohos_react_nativeohos_react_native
React Native鸿蒙化仓库
C++
176
260
CangjieCommunityCangjieCommunity
为仓颉编程语言开发者打造活跃、开放、高质量的社区环境
Markdown
1.07 K
0
Cangjie-ExamplesCangjie-Examples
本仓将收集和展示高质量的仓颉示例代码,欢迎大家投稿,让全世界看到您的妙趣设计,也让更多人通过您的编码理解和喜爱仓颉语言。
Cangjie
331
1.08 K