【问题标题】:Cleaning Unwanted Fields From GraphQL Responses从 GraphQL 响应中清除不需要的字段
【发布时间】:2018-04-23 00:47:56
【问题描述】:

我有一个 GraphQL 客户端请求的对象。

这是一个相当简单的对象:

type Element {
    content: [ElementContent]
    elementId: String
    name: String
    notes: String
    type: String
    createdAt: String
    updatedAt: String
  }

使用特殊类型ElementContent,它很小,看起来像这样:

  type ElementContent {
    content: String
    locale: String
  }

现在,当我在客户端查询此内容时,顶级对象和较低级别对象都有其他属性(如果我尝试按原样克隆主体,则会干扰更新对象);

值得注意的是,GraphQL 似乎在父对象中提供了 __typename 属性,而在子对象中,它们也具有 typename 和 Symbol(id) 属性。

我很想将此对象复制到状态,更新状态,然后克隆状态并将其发送到我的 update 突变。但是,由于 GraphQL 本身提供的未知属性,我遇到了障碍。

我已经尝试过:

delete element.__typename 效果很好,但是我还需要遍历子项(一个动态的对象数组),并且可能还必须删除这些属性。

我不确定我是否在这个等式中遗漏了一些东西,或者我应该只是在代码和循环 + 删除中挣扎(我最初收到错误尝试执行 forEach 循环)。对于我正在尝试做的事情,是否有更好的策略?还是我走在正确的道路上,只需要一些好的循环代码来清除不需要的属性?

【问题讨论】:

标签: javascript ecmascript-6 graphql


【解决方案1】:

有三种方法可以做到这一点

第一种方式

像这样更新客户端参数,它将省略graphql中不需要的字段。

apollo.create({
  link: http,
  cache: new InMemoryCache({
    addTypename: false
  })
});

第二种方式

通过使用omit-deep 包并将其用作中间件

const cleanTypeName = new ApolloLink((operation, forward) => {
  if (operation.variables) {

    operation.variables = omitDeep(operation.variables,'__typename')
  }
  return forward(operation).map((data) => {
    return data;
  });
});

第三条路

创建自定义中间件并注入 apollo

const cleanTypeName = new ApolloLink((operation, forward) => {
  if (operation.variables) {
    const omitTypename = (key, value) => (key === '__typename' ? undefined : value);
    operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename);
  }
  return forward(operation).map((data) => {
    return data;
  });
});

并注入中间件

const httpLinkWithErrorHandling = ApolloLink.from([
  cleanTypeName,
  retry,
  error,
  http,
]);

如果您使用带有查询/变异的片段,建议使用 Second WayThird Way

首选方法是Third Way,因为它没有任何第三方依赖,也没有缓存性能问题

【讨论】:

  • 很遗憾,它对我不起作用,__typename 不在operation.variables 内。
  • 为什么要使用发病率图data => data
  • 抱歉混淆了,我在问你为什么要使用 .map 来将 data 映射到自身,就像在 .map((data) => { return data }) 中一样 - 我几乎在每个示例中都看到了它,但我没有明白它。它不会以任何方式操纵任何东西。
  • 第一种方法适用于我的 Angular8 客户端。谢谢!!
  • 不幸的是,这个答案不适用于许多现实生活中的案例。首先,问题是关于 GQL responses,选项 2 和 3 只修改操作变量(发送到服务器),而不是响应。只有选项 1 会涵盖两者(可能),但是还有另一个问题:当架构深度嵌套和/或使用片段时,每个选项都会破坏缓存,这依赖于__typename。总而言之,这里没有好的包罗万象的解决方案。一些选项可能适用于您的情况,但如果您使用片段和/或缓存,您肯定只需要使用__typename
【解决方案2】:

如果您想从 GraphQL 响应(从根及其子级)中清除 __typename,可以使用 graphql-anywhere 包。

类似: const wipedData = filter(inputFragment, rcvData);

  • inputFragment是定义字段的片段(可以看详情here
  • rcvData 是从 GraphQL 查询接收到的数据

通过使用filter 函数,wipedData 仅包含您需要作为突变输入传递的必填字段。

【讨论】:

    【解决方案3】:

    这就是我所做的,也支持文件上传。这是我在 Github 线程上找到的多个建议的合并:Feature idea: Automatically remove __typename from mutations

    import { parse, stringify } from 'flatted';
    
    const cleanTypename = new ApolloLink((operation, forward) => {
        const omitTypename = (key, value) => (key === '__typename' ? undefined : value);
    
        if ((operation.variables && !operation.getContext().hasUpload)) {
            operation.variables = parse(stringify(operation.variables), omitTypename);
        }
    
        return forward(operation);
    });
    

    连接我的 client.tsx 文件的其余部分,简化:

    import { InMemoryCache } from 'apollo-cache-inmemory';
    import { createUploadLink } from 'apollo-upload-client';
    import { ApolloClient } from 'apollo-client';
    import { setContext } from 'apollo-link-context';
    import { ApolloLink } from 'apollo-link';
    
    const authLink = setContext((_, { headers }) => {
        const token = localStorage.getItem(AUTH_TOKEN);
        return {
            headers: {
            ...headers,
            authorization: token ? `Bearer ${ token }` : '',
            },
        };
    });
    
    
    
    const httpLink = ApolloLink.from([
        cleanTypename,
        authLink.concat(upLoadLink),
    ]);
    
    const client = new ApolloClient({
        link: httpLink,
        cache,
    });
    
    export default client;
    

    现在当我调用上传类型的突变时,我只需将上下文 hasUpload 设置为 true,如下所示:

    UpdateStation({variables: { input: station }, context: {hasUpload: true }}).then()
    

    【讨论】:

      【解决方案4】:

      对于那些寻找 TypeScript 解决方案的人:

      import cloneDeepWith from "lodash/cloneDeepWith";
      
      export const omitTypenameDeep = (
        variables: Record<string, unknown>
      ): Record<string, unknown> =>
        cloneDeepWith(variables, (value) => {
          if (value && value.__typename) {
            const { __typename, ...valWithoutTypename } = value;
            return valWithoutTypename;
          }
      
          return undefined;
        });
      
      const removeTypename = new ApolloLink((operation, forward) => {
        const newOperation = operation;
        newOperation.variables = omitTypenameDeep(newOperation.variables);
        return forward(newOperation);
      });
      
      // ...
      
      const client = new ApolloClient({
        cache: new InMemoryCache(),
        link: ApolloLink.from([removeTypename, httpLink]),
      });
      

      【讨论】:

      • 您的函数omitTypenameDeep 不正确,因为它不会进行深度克隆(递归)。更正:ts const omitTypenameDeep = ( variables: Record&lt;string, unknown&gt; ): Record&lt;string, unknown&gt; =&gt; cloneDeepWith(variables, (value) =&gt; { if (value?.__typename) { const { __typename, ...valWithoutTypename } = value return omitTypenameDeep(valWithoutTypename) } return undefined })
      【解决方案5】:

      我刚刚发布了graphql-filter-fragment 来帮助解决这个用例。我有 wrote in a bit more detail about the CRUD use case I had 导致我采用这种方法。

      例子:

      import {filterGraphQlFragment} from 'graphql-filter-fragment';
      import {gql} from '@apollo/client/core';
      
      const result = filterGraphQlFragment(
        gql`
          fragment museum on Museum {
            name
            address {
              city
            }
          }
        `,
        {
          __typename: 'Museum',
          name: 'Museum of Popular Culture',
          address: {
            __typename: 'MuseumAddress',
            street: '325 5th Ave N',
            city: 'Seattle'
          }
        }
      );
      
      expect(result).toEqual({
        name: 'Museum of Popular Culture',
        address: {
          city: 'Seattle'
        }
      });
      

      【讨论】:

        猜你喜欢
        • 2013-04-17
        • 2022-01-12
        • 2017-06-04
        • 2015-11-05
        • 1970-01-01
        • 2021-06-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多