【问题标题】:How to modify parent node before it is passed to a resolver?如何在将父节点传递给解析器之前修改父节点?
【发布时间】:2019-12-05 12:25:55
【问题描述】:

假设我们有这个 GraphQL 架构:

type Venue implements Node {
  country: Country!
  id: ID!
  name: String!
  nid: String!
  url: String!
}

此解析器支持:

// @flow

import type {
  VenueRecordType,
  ResolverType,
} from '../types';

const Venue: ResolverType<VenueRecordType> = {
  country: (node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  },
};

export default Venue;

我希望能够在解析器字段使用父/节点参数值之前对其进行修改。

据我所知,阅读文档后,实现此目的的唯一方法是实现和包装每个字段,例如

// @flow

import type {
  VenueRecordType,
  ResolverType,
} from '../types';

const createNodeDecorator = (fieldResolver) => {
  const updateNode = (node) => {
    // https://media0.giphy.com/media/12NUbkX6p4xOO4/giphy.gif
    return node;
  };

  return (parent, parameteres, context, info) => {
    return fieldResolver(updateNode(parent), parameteres, context, info);
  };
};

const Venue: ResolverType<VenueRecordType> = {
  country: createNodeDecorator((node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  }),
  id: createNodeDecorator((node) => {
    return node.id;
  }),
  name: createNodeDecorator((node) => {
    return node.name;
  }),
  nid: createNodeDecorator((node) => {
    return node.nid;
  }),
  url: createNodeDecorator((node) => {
    return node.url;
  }),
};

export default Venue;

有没有更好的办法?

理想情况下,我会在使用解析器之前调用一个 __load 挂钩,例如

const Venue: ResolverType<VenueRecordType> = {
  __load: (parent, parameteres, context, info, next) => {
    next(parent, parameteres, context, info);
  },
  country: (node, parameters, context) => {
    return context.loaders.CountryByIdLoader.load(node.countryId);
  },
};

但这(据我所知)不存在。

在传递给解析器之前如何修改父节点?

【问题讨论】:

  • ????因为它不仅提供了一个最小的、可复制的,而且还设法在那里的模因中工作

标签: graphql apollo apollo-server


【解决方案1】:

执行此操作的一种方法是将修改节点的逻辑上移一个级别。例如,给定一个查询类型,如:

type Query {
  venues: [Venue!]!
}

我们可以在解析器中执行以下操作:

const resolvers: {
  Query: {
    venues: async (root, args, context) => {
      const venues = await context.loaders.VenueLoader.load()
      return venues.map(magic)
    }
  }
}

这可行,但这意味着您必须在返回场地或场地列表的任何解析器中复制逻辑,这既乏味又容易出错。如果您已经在使用加载器,我只需将这个逻辑移到加载器本身中就可以了。

但是,我们可以更进一步,也可以使用模式指令。例如,如果您想为不同的类型重用相同的逻辑,或者出于某种奇怪的原因,您想仅在某些字段上修改父级,这将很有帮助。这是一个示例,可让您将指令应用于类型或单个字段:

class MagicDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field
    field.resolve = function (source, args, context, info) {
      return resolve.apply(this, [magic(source), args, context, info])
    }
  }
  visitObject(object) {
    const fieldMap = object.getFields()
    for (const fieldName in fieldMap) {
      this.visitFieldDefinition(fieldMap[fieldName])
    }
  }
}

然后将指令作为schemaDirectives 的一部分传递给您的ApolloServer 配置,并将其包含在您的类型定义中:

directive @magic on FIELD_DEFINITION | OBJECT

【讨论】:

  • 在你回答之前从来没有花足够的时间来学习模式指令。他们很棒!我的用例是实现本文中描述的解析器模式。 medium.com/paypal-engineering/… 实际上,假设父节点只返回 ID - 解析器对象来获取它们的数据。正如您所提到的,这减少了父节点中的代码重复。
  • @Gajus 感谢分享那篇文章。这一个非常酷的模式,特别是如果您已经在使用DataLoader。 FWIW,我不认为接受这种模式需要你明确地修改你的父值。如果您已经获得了“完整”对象但只需要 id,那么除了额外的开销之外,您不会从转换对象中获得任何收益。
  • @Gajus 我已经尝试过这种方法,我认为那篇文章推荐了一个巨大的反模式。如果对象不存在会怎样?你从你的父母那里返回了一些东西,所以你说这个对象存在。如果对象不存在,正确的响应是 null,而您现在已经让这成为不可能了。
  • 如果没有什么可返回的,如何从父级返回?
  • 这是他的建议:venueById(parent, args) { return { id: args.id }; }。他已经接受了用户的输入并返回了对象而不进行查找
猜你喜欢
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
  • 2021-11-23
  • 2020-05-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多