使用GraphQL触发嵌套的解析器 [英] Trigger nested resolvers with GraphQL

查看:60
本文介绍了使用GraphQL触发嵌套的解析器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为我在GraphQL解析器的工作方式中缺少明显的东西.这是我的 schema (可以包含 AdditionalInformation Place )的简化示例:

 从'apollo-server'导入{ApolloServer,gql};const typeDefs = gql`输入Place {名称:字符串!AdditionalInformation:附加信息}类型AdditionalInformation {foo:字串}输入查询{地点:[地点]}`; 

以及关联的解析器:

 常量解析器= {询问: {地方:()=>{返回[{name:'Barcelona'}];}},附加信息: {foo:()=>'酒吧'}};const server = new ApolloServer({typeDefs,resolvers});server.listen().then(({{url})=> {console.log(`API服务器已在$ {url}`就绪);}); 

当我执行基本查询时:

  {地方{名称,附加信息 {富}}} 

我总是得到 null 作为 additionalInformation :

  {数据": {地点":[{"name":"Barcelona","additionalInformation":null}]}} 

这是我的第一个GraphQL应用程序,但我仍然不明白为什么 AdditionalInformation 解析器不会自动执行.有什么方法可以让GraphQL知道它必须被触发吗?

我已经找到了解决方法,但是我觉得有些棘手:

 位置:{AdditionalInformation:()=>{ 返回 {};}}} 

解决方案

让我们暂时假设 additionalInformation 是标量,而不是对象类型:

  type Place {名称:字符串!AdditionalInformation:字符串} 

places 解析器返回的值是:

  [{{name:'Barcelona'}]] 

如果要进行类似的查询...

 查询{地方{名称附加信息}} 

您期望 additionalInformation 是什么?该值将为null,因为 places 解析程序返回的 Place 对象上没有 additionalInformation 属性.

即使我们将 additionalInformation 设置为对象类型(例如 AdditionalInformation ),结果也是相同的- additionalInformation 字段将解析为空值.这是因为默认解析器(当您未为字段指定解析器功能时使用的解析器)只是在寻找与父对象上的字段具有相同名称的属性.如果找不到该属性,则返回null.

您可能已经为 AdditionalInformation ( foo )上的一个字段指定了解析器,但是此解析器从未触发过,因为不需要-整个 additionalInformation字段为空,因此将跳过所有关联类型的任何字段的解析器.

要了解为什么这是理想的行为,请想象一个不同的模式:

  type文章{标题:字符串!内容:字符串!图片:图片}键入图片{网址:字符串!版权:字符串!}输入查询{文章:[文章!]!} 

我们有一个数据库,其中有一个 articles 表和一个 images 表作为我们的数据层.文章可能有也可能没有与之相关的图像.我的解析器可能看起来像这样:

  const解析器= {询问: {文章:()=>db.getArticlesWithImages()}图像: {版权:(image)=>`©$ {image.year} $ {image.author}`}} 

比方说,我们的调用 getArticlesWithImages 解析为没有图像的单篇文章:

[{title:'Foo',内容:'All about foos'}]

作为API的使用者,我要求:

 查询{文章{标题内容图像}} 

image 字段是可选的.如果返回带空 image 字段的文章对象,则说明数据库中没有关联的图像.作为前端客户端,我知道不渲染任何图像.

如果GraphQL返回 image 的值,该怎么办?显然,我们的解析器会中断,因为不会传递任何父值.而且,但是,作为API的使用者,我现在必须解析 image 的内容,并以某种方式确定图像是否实际上与文章相关联,我应该对此做些什么./p>

TLDR;

正如您已经建议的那样,此处的解决方案是为 additionalInfo 指定解析器.您也可以简单地在 places 解析器中返回该值,即:

 返回[{name:'Barcelona',AdditionalInfo:{}}] 

实际上,如果架构的形状与基础数据层的形状对齐,则在处理真实数据时不太可能会遇到此类问题.

I think I'm missing something obvious in the way GraphQL resolvers work. This is a simplified example of my schema (a Place that can have AdditionalInformation):

import { ApolloServer, gql } from 'apollo-server';

const typeDefs = gql`
  type Place {
    name: String!
    additionalInformation: AdditionalInformation
  }

  type AdditionalInformation {
    foo: String
  }

  type Query {
    places: [Place]
  }
`;

And the associated resolvers:

const resolvers = {
  Query: {
    places: () => {
        return [{name: 'Barcelona'}];
    }
  },
  AdditionalInformation: {
    foo: () => 'bar'
  }
};

const server = new ApolloServer({typeDefs, resolvers});

server.listen().then(({ url }) => {
  console.log(`API server ready at ${url}`);
});

When I execute a basic query:

{
  places {
    name,
    additionalInformation {
      foo
    }
  }
}

I always get null as the additionalInformation:

{
  "data": {
    "places": [
      {
        "name": "Barcelona",
        "additionalInformation": null
      }
    ]
  }
}

It's my first GraphQL app, and I still don't get why the AdditionalInformation resolver is not automatically executed. Is there some way to let GraphQL know it has to fire it?

I've found this workaround but I find it a bit tricky:

Place: {
  additionalInformation: () => { return {}; }
}}

解决方案

Let's assume for a moment that additionalInformation was a Scalar, and not an Object type:

type Place {
  name: String!
  additionalInformation: String
}

The value returned by the places resolver is:

[{name: 'Barcelona'}]

If you were to make a similar query...

query {
  places {
    name
    additionalInformation
  }
}

What would you expect additionalInformation to be? It's value will be null because there is no additionalInformation property on the Place object returned by the places resolver.

Even if we make additionalInformation an Object type (like AdditionalInformation), the result is the same -- the additionalInformation field will resolve to null. That's because the default resolver (the one used when you don't specify a resolver function for a field) simply looks for a property with the same name as the field on the parent object. If it fails to find that property, it returns null.

You may have specified a resolver for a field on AdditionalInformation (foo), but this resolver is never fired because there's no need -- the whole additionalInformation field is null so all of the resolvers for any fields of the associated type are skipped.

To understand why this is a desirable behavior, imagine a different schema:

type Article {
  title: String!
  content: String!
  image: Image
}

type Image {
  url: String!
  copyright: String!
}

type Query {
  articles: [Article!]!
}

We have a database with an articles table and an images table as our data layer. An article may or may not have an image associated with it. My resolvers might look like this:

const resolvers = {
  Query: {
    articles: () => db.getArticlesWithImages()
  }
  Image: {
    copyright: (image) => `©${image.year} ${image.author}`
  }
}

Let's say our call getArticlesWithImages resolves to a single article with no image:

[{ title: 'Foo', content: 'All about foos' }]

As a consumer of the API, I request:

query {
  articles {
    title
    content
    image
  }
}

The image field is optional. If I get back an article object with a null image field, I understand there was no associated image in the db. As a front end client, I know not to render any image.

What would happen if GraphQL returned a value for the image regardless? Obviously, our resolver would break, since it would not be passed any kind of parent value. Moreover, however, as a consumer of the API, I would have to now parse the contents of image and somehow determine whether an image was in fact associated with the article and I should do something with it.

TLDR;

As you already suggested, the solution here is to specify a resolver for additionalInfo. You can also simply return that value in your places resolver, i.e.:

return [{name: 'Barcelona', additionalInfo: {}}]

In reality, if the shape of your schema aligns with the shape of your underlying data layer, it's unlikely you'll encounter this sort of issue when working with real data.

这篇关于使用GraphQL触发嵌套的解析器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆