Apollo boost - 查询中的 __typename 防止新的突变

Posted

技术标签:

【中文标题】Apollo boost - 查询中的 __typename 防止新的突变【英文标题】:Apollo boost - __typename in query prevent new mutation 【发布时间】:2019-03-23 05:54:47 【问题描述】:

我的流星/反应/阿波罗(带升压)项目有问题。当我从服务器查询数据时,它会将 __typename 添加到查询中的每个对象和子对象中,但在我的情况下,它会产生一个主要问题,因为我通常会重用这些数据以将它们发送到其他突变。现在另一个突变告诉我有一个错误,因为 __typename 字段未在我的 graphql 架构中定义。

我尝试通过将 addTypename: false 字段添加到我的 apollo 客户端来进行修复,但它没有改变任何东西(注意我正在使用 apollo boost,这可能是它无法正常工作的原因):

const client = new ApolloClient(
    uri: Meteor.absoluteUrl('graphql'),
    addTypename: false,
    request: operation =>
        operation.setContext(() => (
            headers: 
                authorization: Accounts._storedLoginToken()
            
        ))
)

此外,即使它有效,它似乎也不是很优化。在我看来,在查询结果中添加了一个字段,我很惊讶没有在网上找到任何明确的解决方案。一些建议的解决方案:

在客户端手动过滤 将中间件添加到 apollo 将 __typename 字段添加到我的所有架构中...

但它们似乎都不适合阿波罗为查询带来的“简单”。我希望提供一个更简单、更合乎逻辑的解决方案,但到目前为止,还没有找到任何解决方案。

【问题讨论】:

【参考方案1】:

您不应该删除 __typename。它们用于缓存,也用于联合类型。可能应该等待阿波罗的更新。 不幸的是我现在也有问题,我找不到合适的解决方案。一开始我只是简单地删除了所有类型名称,但现在我遇到了联合类型的问题。

【讨论】:

【参考方案2】:

从 here。 '__typename' 可以使用以下辅助函数删除

const cleanedObject = omitDeep(myObject, "__typename")

const omitDeep = (obj, key) => 
    const keys = Object.keys(obj);
    const newObj = ;
    keys.forEach((i) => 
      if (i !== key) 
        const val = obj[i];
        if (val instanceof Date) newObj[i] = val;
        else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key);
        else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key);
        else newObj[i] = val;
      
    );
    return newObj;
  ;

const omitDeepArrayWalk = (arr, key) => 
  return arr.map((val) => 
    if (Array.isArray(val)) return omitDeepArrayWalk(val, key)
    else if (typeof val === 'object') return omitDeep(val, key)
    return val
  )

【讨论】:

如何将此代码与 apollo 客户端一起使用,我们是否应该启用名为 addTypeName 的字段为 true【参考方案3】:

即使使用apollo-client 而不是apollo-boost,您也不应该将addTypename 设置为false,除非您有令人信服的理由这样做。 __typename 字段被 InMemoryCache 用于规范化查询结果,因此省略它可能会导致缓存出现意外行为。

不幸的是,这个问题没有“灵丹妙药”。请求查询然后将该查询的数据用作其他查询的变量可能被解释为滥用 API。查询返回的Type 和用作参数的Input Type 是完全不同的东西,即使它们作为javascript 对象共享一个或多个字段。就像您不能在模式中互换使用类型和输入类型一样,也不应该期望它们可以在客户端互换使用。

这也意味着,如果您发现自己处于这种情况,您可能需要重新审视一下您的架构设计。毕竟,如果数据已经存在于服务器上,传入它的 id 并在服务器端检索它就足够了,而不必传入整个对象。

如果您使用某个查询来填充一个或多个输入,然后在突变中使用这些输入的值,那么您可能已经将初始查询数据转换为组件状态,然后在您的突变中使用它。在这种情况下,__typename 或任何其他不可编辑的字段可能首先不应该作为组件状态的一部分。

在一天结束时,希望进行此类操作将成为例外,而不是规则。我会创建某种辅助函数来“清理”您的输入并继续前进。

function stripTypenames (value) 
    if (Array.isArray(value)) 
        return value.map(stripTypenames)
     else if (value !== null && typeof(value) === "object") 
      const newObject = 
      for (const property in value) 
          if (property !== '__typename') 
            newObject[property] = stripTypenames(value[property])
          
      
      return newObject
     else 
      return value
    

【讨论】:

丹尼尔,再次感谢您快速而清晰的回答。我确实在重用组件状态的数据,如果需要修改它们,然后将它们发回,因此我必须按照你提到的那样过滤它们,所以我猜这个解决方案是正确的解决方案。但是,我真的很怀疑 graphQL/Apollo 是如何处理这个问题的。对于一个应该直截了当并只提供请求的数据的 API,向每个对象和子对象添加一个字段似乎有点矫枉过正并且违背了整个要点。很多 github 问题请求似乎重新加入了这一观点。 添加__typename 的原因是为了启用规范化缓存。查看(文档)[apollographql.com/docs/react/advanced/… 的规范化部分。如果您的所有查询都是network-only 或者您想实现自己的dataIdFromObject 函数,那么您可以将addTypename 设置为false。 我并不是说这是对还是错,但 Apollo 对如何根据约定使用 API 做出了某些假设。这反映在 QueryMutation 组件功能不同的方式上——即使查询和突变都可以做同样的事情,但假设查询将获取数据,而突变将改变数据。同样,我相信,有一个假设是查询的数据不会被用作另一个查询的输入,因为这将是非常规的,即使在某些情况下在技术上是可行的。 如果有人对我如何解决它感兴趣:我使用 lodash 中的 omit 函数对每个对象(和子对象)在我从 apollo 查询数据之后和设置之前删除“__typename”键他们进入反应状态。因此,我可以修改它们并再次将反应状态发送到 Mutation 以在需要时保存新数据。

以上是关于Apollo boost - 查询中的 __typename 防止新的突变的主要内容,如果未能解决你的问题,请参考以下文章

ES 搜索—— 常用查询语句

@Nuxt/Apollo 如何从 gql 查询中删除“__typeName”

Vue Apollo 上传文件崩溃节点最大调用堆栈大小超过 _openReadFs

如何选择性地允许 apollo-server-express 中的查询?

在单独的查询中加载 __typename 字段 Apollo-Client 未更新 UI

Apollo-boost 缓存问题