使用GraphQL触发嵌套的解析器 [英] Trigger nested resolvers with 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屋!