Apollo mutation 返回条目只剩 __typename,检查 possibleTypes
服务端已返回interface fragment字段,Apollo4.3的mutation结果却被缓存读取裁去。补齐实际缓存的类型映射,再核对返回字段、更新回调及持久化缓存,不重复mutation。
GraphQL 网络响应里的商品有 key、quantity,await client.mutate() 得到的条目却只剩 __typename。如果字段来自定义在 interface 上的 fragment,而 InMemoryCache 没有对应的 possibleTypes,先检查客户端的类型关系配置,不要重复提交那次 mutation。
这组变化在 Apollo Client 4.3.0 至 4.3.3 的默认 mutation 路径中可见:响应写入后重新经过缓存读取,缺少抽象类型映射的 fragment 不再出现在返回对象里。4.2.12 的 query 已有同样要求;旧版 mutation 配了 update 时也可能暴露它。不能把“旧 mutation 曾返回完整数据”理解为该配置一直有效。
__typename 有值,不等于缓存知道接口实现关系
假设 GraphQL schema 声明 CartItem 为 interface,SimpleCartItem 是其中一个实现类型:
fragment ItemFields on CartItem {
key
quantity
}
服务端返回 __typename: "SimpleCartItem",缓存仍需要知道 SimpleCartItem 属于 CartItem。没有这项信息时,fragment 的类型条件无法仅靠字段长相匹配。网络响应里有字段,与缓存按这个 fragment 能读到字段,是两项检查。
先保留脱敏的网络结果,对照操作中的 fragment 类型条件、具体对象的 __typename,以及创建当前客户端的 cache 配置。若网络结果本来就没有 quantity,应继续查 GraphQL 选择集、权限与服务端结果;不能用 possibleTypes 生成原本没传来的数据。
把真实 schema 的抽象类型映射交给同一个 cache
例如项目已导入 InMemoryCache 时,可以将相应配置加入实际创建 ApolloClient 的那个缓存实例:
const cache = new InMemoryCache({
possibleTypes: {
CartItem: ['SimpleCartItem'],
},
// 保留项目原有的 typePolicies 等配置
});
这只是一个仅含单一实现类型的示例。真实 CartItem 若还有其他实现,都需要按服务端 schema 列出;union 也要列出其成员。不要为了当前商品能显示,就把所有不相关类型都塞进 CartItem。
schema 较多时,可在既有代码生成流程里生成 possibleTypes,并让客户端与服务端 schema 版本同步。还要确认页面和 mutation 使用的确实是这个 cache。只改一个未被实例化的配置文件,或给另一个独立 ApolloClient 增加映射,不会改变当前结果。
既有持久化缓存与新配置的衔接,应沿项目的缓存迁移流程处理。字段已经没有存进去时,可以通过获准的只读查询补取;不要再次执行加购物车、扣库存等 mutation 来“刷新显示”。也不要未经评估清空所有用户的客户端状态。
no-cache 只能作为有明确取舍的局部绕行
对单次 mutation 设置 fetchPolicy: 'no-cache',可以绕开这条常规缓存写读路径,但会改变更新缓存和相关界面观察的行为。依赖 mutation 更新已订阅查询、update 或自定义 scalar 的应用,应逐项评估,不能把全站关闭缓存当成缺映射的修复。
当前正式 4.3.5 仍按抽象类型映射匹配 fragment,并在正常 mutation 路径中进行缓存写读。升级依赖不会代替服务端 schema 的 possibleTypes 配置。
修订后,在受控数据中核对网络结果、mutation 返回值和同一个 fragment 的 query 读取;再覆盖各实际实现类型、正常 refetch 和现有更新回调。字段应在需要的位置存在,业务动作只执行约定次数。若只有一次网络返回完整,而之后查询仍只剩 __typename,还不能算这条缓存读取链路已恢复。


