Skip to main content

Customizing the GraphQL Schema

Gatsby的主要优势之一是能够使用GraphQL以统一的方式查询来自各种来源的数据. 为此,必须生成定义数据形状的GraphQL模式.

Gatsby能够根据您的数据自动推断出GraphQL模式,在很多情况下,这实际上是您所需要的. 但是,在某些情况下,您要么想要明确定义数据形状,要么向查询层添加自定义功能-这就是Gatsby的Schema Customization API提供的功能.

以下指南逐步介绍了一些示例,以展示API.

本指南面向插件作者,试图修复由自动类型推断创建的GraphQL模式的用户,开发人员针对较大站点优化构建的开发人员以及对自定义Gatsby模式生成感兴趣的任何人. 因此,本指南假定您对GraphQL类型和使用Gatsby的Node API有所了解. 有关将Gatsby与GraphQL结合使用的更高级方法,请参阅API参考 .

Explicitly defining data types

示例项目是一个博客,该博客从本地Markdown文件获取其数据,该文件提供了帖子的内容以及JSON格式的作者信息. 偶尔也有访客贡献者,其信息保存在单独的JSON文件中.

为了能够使用GraphQL查询这些文件的内容,首先需要将它们加载到Gatsby的内部数据存储中. 这就是源代码和转换器插件的功能-在本例中为gatsby-source-filesystemgatsby-transformer-remark加上gatsby-transformer-json . 因此,每个markdown发布文件都转换为内部数据存储中具有唯一id和类型MarkdownRemark的"节点"对象. 类似地,作者将由AuthorJson类型的节点对象表示,并且参与者信息将转换为ContributorJson类型的节点对象.

The Node interface

Gatsby的GraphQL模式通过Node接口表示此数据结构,该接口描述了由源插件和转换器插件创建的节点对象( idparentchildren以及一些internal字段,如type )所共有的字段集. 在GraphQL架构定义语言(SDL)中,它看起来像这样:

由源插件和转换器插件创建的类型实现此接口. 例如,由gatsby-transformer-jsonauthors.json创建的节点类型将在GraphQL模式中表示为:

GraphQL Playground是检查Gatsby生成的架构的快速方法. 使用GATSBY_GRAPHQL_IDE=playground gatsby develop开始您的项目,打开位于http:// localhost:8000 / ___ graphql的Playground,然后检查右侧的Schema选项卡.

Automatic type inference

重要的是要注意author.json中的数据本身并不提供Author字段的类型信息. 为了将数据形状转换为GraphQL类型定义,Gatsby必须检查每个字段的内容并检查其类型. 在许多情况下,这种方法效果很好,它仍然是创建GraphQL模式的默认机制.

但是,这种方法存在两个问题:(1)非常耗时,因此无法很好地扩展;(2)字段上的值是否为不同类型,盖茨比无法确定哪一个是正确的. 这样的结果是,如果您的数据源发生更改,则类型推断可能会突然失败.

通过为Gatsby的GraphQL模式提供显式类型定义,可以解决这两个问题.

Creating type definitions

首先看后一种情况. 假设有一位新作者加入团队,但是在新作者条目中, joinedAt字段中有一个错字:" 201-04-02",这不是有效的日期.

这将混淆Gatsby的类型推断,因为joinedAt字段现在将同时具有Date和String值.

Fixing field types

为确保该字段始终为日期类型,您可以使用createTypes操作为盖茨比提供明确的类型定义. 它接受GraphQL架构定义语言中的类型定义:

请注意,不必提供其余字段( namefirstName等),它们仍将由Gatsby的类型推断来处理.

createSchemaCustomization (在Gatsby v2.12及更高版本中可用)和sourcesNodes API中提供了用于自定义Gatsby模式生成的操作.

Opting out of type inference

There are however advantages to providing full definitions for a node type, and bypassing the type inference mechanism altogether. With smaller scale projects inference is usually not a performance problem, but as projects grow the performance penalty of having to check each field type will become noticeable.

Gatsby允许选择不使用@dontInfer类型指令进行推断-反过来,这需要您为所有可用于查询的字段显式提供类型定义:

请注意,您不需要显式提供Node接口字段( idparent等),Gatsby会自动为您添加它们.

如果您对感叹号感到好奇-那些允许在GraphQL中指定可为空性 ,即是否允许将字段值设置为null .

Nested types

到目前为止,示例项目仅处理标量值( StringDate ; GraphQL还知道IDIntFloatBooleanJSON ). 但是,字段也可以包含复杂的对象值. 要针对GraphQL SDL中的这些字段,您可以为嵌套类型提供完整的类型定义,可以随意命名(只要名称在架构中是唯一的)即可. 在示例项目中, MarkdownRemark节点类型上的frontmatter字段是一个很好的示例. 假设您要确保frontmatter.tags始终是字符串数组.

请注意,与createTypes你不能直接靶向Frontmatter类型的同时没有指定,这是类型frontmatter场上MarkdownRemark类型,下面将失败,因为盖茨将无法知道哪个字段的方式Frontmatter类型应适用于:

通过始终从源插件和转换器插件创建的Node类型开始,考虑数据和相应的GraphQL模式很有用.

请注意, Frontmatter类型不能实现Node接口,因为它不是由源插件或转换器插件创建的顶级类型:它没有id字段,并且仅用于描述嵌套字段上的数据形状.

Gatsby Type Builders

在许多情况下,GraphQL SDL提供了一种简洁的方法来为架构提供类型定义. 但是,如果需要更大的灵活性, createTypes也接受Gatsby Type Builders提供的类型定义,它们比SDL语法更灵活,但比graphql-js更为冗长. 在传递给Node API的schema参数上可以访问它们.

Gatsby类型构建器允许将类型引用为简单字符串,并接受完整字段配置( typeargsresolve ). 在定义顶级类型时,请不要忘记传递interfaces: ['Node'] ,它对类型生成器的作用与添加implements Node的SDL定义类型的作用相同. 通过将infer类型扩展名设置为false还可以选择不使用类型构建器进行类型infer

还存在用于输入,接口和联合类型的类型构建器: buildInputTypebuildInterfaceTypebuildUnionType . 请注意, createTypes操作也直接接受graphql-js类型,但通常SDL或Type Builders是更好的选择.

Foreign-key fields

Gatsby的自动类型推断有一个窍门:对于以___NODE结尾的每个字段,它将字段值解释为id并创建外键关系.

注意:在Gatsby v2.2中引入模式自定义API之前,有两种机制可在节点类型之间创建链接:插件作者将使用___NODE字段名约定(用于插件),而用户将在其中定义字段之间的映射他们的gatsby-config.js . 用户和插件作者现在都可以使用下面描述的@link扩展名.

使用createTypes操作创建外键关系,即不依赖于类型推断和___NODE字段命名约定,需要一些手动设置.

在示例项目中, MarkdownRemark节点上的frontmatter.author字段将提供的字段值扩展到完整的AuthorJson节点. 为此,必须提供一个自定义字段解析器. (有关context.nodeModel更多信息,请参见下文)

这里发生的是,您提供了一个自定义字段解析器,该解析器向Gatsby的内部数据存储区询问具有指定idtype的完整节点对象.

因为创建外键关系是一个常见的用例,所以幸运的是,在扩展或指令的帮助下,盖茨比还提供了一种更简单的方法来实现此目的. 看起来像这样:

您在字段上提供了@link指令,Gatsby会在内部添加一个与上面手动编写的解析器非常相似的解析器. 如果不提供参数,盖茨将使用id字段作为外键,否则外键必须提供与by争论. 可选的from参数允许获取当前类型的字段,该字段充当by指定的字段的外键. 换句话说,您link from by . 这使得from增加对回联字段时特别有用.

请注意,在使用createTypes修复由插件创建的外键字段的类型推断时,基础数据可能会存在于带有___NODE后缀的字段中. 使用from参数将link扩展指向正确的字段名称. 例如: author: [AuthorJson] @link(from: "author___NODE") .

Extensions and directives

开箱即用的Gatsby提供了四个扩展 ,这些扩展允许在无需手动编写字段解析器的情况下向字段添加自定义功能: link扩展已在上文中进行了讨论, dateformat允许添加日期格式设置选项, fileByRelativePathlink相似,但将解析相对路径链接到File节点时, proxy在处理包含字段名称和在GraphQL中无效的字符的数据时很有用.

要将扩展添加到字段中,可以在SDL中使用指令,也可以在使用Gatsby类型生成器时使用extensions属性:

上面的示例将日期格式设置选项添加到AuthorJson.joinedAtMarkdownRemark.frontmatter.publishedAt字段. 查询这些字段时,这些选项可用作字段参数:

还提供了publishedAt的默认formatString ,当查询中未提供任何显式格式设置选项时将使用该默认格式.

Setting default field values

为了设置默认字段值,Gatsby当前(尚未)提供现成的扩展名,因此要将字段解析为默认值(而不是null )需要手动添加字段解析器. 例如,要将默认标签添加到每个博客文章中:

Creating custom extensions

通过createFieldExtension动作,可以定义自定义扩展名,作为向字段添加可重用功能的一种方式. 假设您要将fullName字段添加到AuthorJsonContributorJson .

您当然可以编写一个fullNameResolver ,并在两个地方使用它:

但是,要使此功能也可用于其他插件并使其在SDL中可用,可以将其注册为字段扩展.

字段扩展定义需要一个名称和一个extend函数,该函数应该返回一个(部分)字段配置(一个对象, type argsresolve ),该配置将合并到现有的字段配置中.

当插件提供自定义字段扩展时,此方法将变得更加强大. 例如,一个非常基本的markdown转换器插件可以提供将markdown字符串转换为html的扩展:

然后,只需将指令/扩展名添加到字段中,就可以在任何createTypes调用中使用它:

请注意,在上面的示例中,还提供了带有args其他配置选项. 例如,这对于提供默认字段参数很有用:

还要注意,字段扩展可以自行决定是否应包装或覆盖现有的字段解析器. 以上示例都决定只返回一个新的resolve函数. 因为extend功能将当前字段配置作为其第二个参数,所以扩展也可以决定包装现有的解析器:

如果将多个字段扩展名添加到一个字段, createTypes以下顺序处理解析器:首先运行添加了createTypes (或createResolvers )的自定义解析器,然后从左到右执行字段扩展解析器.

Finally, note that in order to get the current fieldValue, you use context.defaultFieldResolver.

createResolvers API

尽管可以使用Gatsby类型生成resolversargsresolvers直接传递给类型定义,但专门为向字段添加自定义解析器而量身定制的替代方法是createResolvers Node API.

请注意, createResolvers允许向类型添加新字段,修改argsresolver —但不能覆盖字段类型. 这是因为createResolvers在模式生成中最后运行,并且修改字段类型将意味着必须重新生成相应的输入类型( filtersort ),而这是您要避免的. 如果可能,应使用createTypes操作指定字段类型.

Accessing Gatsby’s data store from field resolvers

如上所述,对于传递给每个解析器的context.nodeModel参数,自定义字段解析器可以使用Gatsby的内部数据存储和查询功能. 访问节点(一个或多个)由id (和任选的type )是可能的getNodeByIdgetNodesByIds . 要获取所有节点或特定类型的所有节点,请使用getAllNodes . 从解析器函数内部运行查询可以通过runQuery完成,该查询接受filtersort查询参数.

例如,您可以向AuthorJson类型添加一个字段,该字段列出作者的所有最新帖子:

使用runQuery对查询结果进行排序时,请注意sort.fieldssort.order都是GraphQLList字段. 此外, sort.fields上的嵌套字段必须以点表示法提供(不能用三位下划线分隔). 例如:

Custom query fields

createResolvers支持的一种强大方法是添加自定义根查询字段. 尽管Gatsby添加的默认根查询字段(例如markdownRemarkallMarkdownRemark )提供了整个查询选项范围,但专门为您的项目设计的查询字段可能会很有用. 例如,您可以为所有收到赃物的示例博客添加所有外部贡献者的查询字段:

因为您可能也对反向感兴趣-哪些贡献者尚未收到赃物-为什么不添加(必需)自定义查询arg?

还可以提供可以在SDL中直接内联定义的更复杂的自定义输入类型. 例如,你可以一个字段添加到ContributorJson类型才是最重要的一个贡献者的帖子数量,然后添加自定义根查询字段contributors它接受minmax参数谁已经至少只写回贡献者min ,或至多max帖子数:

Taking care of hot reloading

创建自定义字段解析器时,重要的是要确保Gatsby知道页面所需要的数据才能使热重装正常工作. 使用context.nodeModel方法从存储中检索节点时,通常无需手动执行任何操作,因为Gatsby会自动注册查询结果的依赖项. getAllNodes是一个例外,默认情况下不会注册数据依赖项. 这是因为当某种类型的任何节点发生更改时,请求重新运行查询可能是非常昂贵的操作. 如果确定确实需要此功能,则可以使用context.nodeModel.trackPageDependencies或以下方式以编程方式添加页面数据依赖项:

Custom Interfaces and Unions

最后,假设您想在示例博客上创建一个页面,其中列出了所有团队成员(作者和贡献者). 你可以做的是有两个查询,一个用于allAuthorJson ,一个用于allContributorJson和手动合并这些. 但是,GraphQL通过"抽象类型"(接口和联合)为此类问题提供了更为优雅的解决方案. 由于作者和贡献者实际上共享大多数字段,因此您可以将它们抽象到TeamMember界面中,并为所有团队成员添加自定义查询字段(以及全名的自定义解析程序):

要在页面查询中使用新添加的根查询字段来获取所有团队成员的全名,您可以编写:

Queryable interfaces with the @nodeInterface extension

Since Gatsby 2.13.22, you can achieve the same thing as above by adding the @nodeInterface extension to the TeamMember interface. This will treat the interface like a normal top-level type that implements the Node interface, and thus automatically add root query fields for the interface.

查询时,对实现接口的类型所专用的字段(即,不共享的字段)使用内联片段:

遍历组件中的查询结果时,包括__typeName自省字段可以检查节点类型:

注意:所有实现带有@nodeInterface扩展名的接口的类型也必须实现Node接口.

Extending third-party types

到目前为止,示例一直在处理从本地可用数据创建的类型. 但是,Gatsby还允许集成和修改第三方GraphQL模式.

通常,这些第三方模式是使用Gatsby的gatsby-source-graphql插件通过内省查询从远程源中检索的. 要自定义从第三方架构集成的类型,可以使用createResolvers API.

Feeding remote images into gatsby-image

例如,您可以查看using-gatsby-source-graphql,以了解如何使用createResolvers将CMS中gatsby-image输入到gatsby-image (假设gatsby-source-graphql已配置为在CMS的第三方架构):

You create a new imageFile field on the CMS_Asset type, which will create File nodes from every value on the url field. Since File nodes automatically have childImageSharp convenience fields available, you can then feed the images from the CMS into gatsby-image by simply querying:


Edit this page on GitHub
Docs
Tutorials
Plugins
Blog
Showcase

by  ICOPY.SITE