Graphql 如何在将父节点传递给冲突解决程序之前修改它?

Graphql 如何在将父节点传递给冲突解决程序之前修改它?,graphql,apollo,apollo-server,Graphql,Apollo,Apollo Server,假设我们有这个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<Ven

假设我们有这个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;

const resolvers: {
  Query: {
    venues: async (root, args, context) => {
      const venues = await context.loaders.VenueLoader.load()
      return venues.map(magic)
    }
  }
}
但这(据我所知)并不存在


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

一种方法是将用于修改节点的逻辑向上移动一级。例如,给定如下查询类型:

type Query {
  venues: [Venue!]!
}
我们可以在解析器内执行以下操作:

// @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;

const resolvers: {
  Query: {
    venues: async (root, args, context) => {
      const venues = await context.loaders.VenueLoader.load()
      return venues.map(magic)
    }
  }
}
这是可行的,但这意味着您必须在任何返回场地或场地列表的解析器中复制逻辑,这既繁琐又容易出错。如果你已经在使用一个加载器,我会把这个逻辑移到加载器本身中,今天到此为止

但是,我们可以更进一步,也可以使用schema指令。例如,如果您希望为不同的类型重用相同的逻辑,或者出于某种奇怪的原因,您希望仅在某些字段上修改父级,那么这将非常有用。下面是一个示例,可以将指令应用于类型或单个字段:

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
config,并将其包含在您的类型定义中:

directive @magic on FIELD_DEFINITION | OBJECT

在回答之前,不要花足够的时间学习模式指令。他们太棒了!我的用例是实现本文中描述的解析器模式。实际上,假设父节点只返回ID–由解析器对象获取其数据。正如您所提到的,这减少了父节点中的代码重复。@Gajus感谢您分享这篇文章。这是一个非常酷的模式,特别是如果您已经在使用DataLoader。FWIW,我不认为接受这种模式需要你明确地修改你的父值。如果您已经获得了一个“完整”的对象,但只需要id,那么除了额外的开销之外,您不会从转换对象中获得任何好处。@Gajus我已经尝试过这种方法,我认为这篇文章推荐了一种巨大的反模式。如果对象不存在怎么办?您已从父级返回了某些内容,因此您的意思是该对象存在。如果一个对象不存在,正确的响应是
null
,而您现在已经不可能了。如果没有什么可以返回,您如何从父对象返回内容呢?这是他建议的:
venueById(parent,args){return{id:args.id};}
。他接受了用户的输入并返回了对象,而没有进行查找