概览
在 Airlock 中,用户可以是 房东 或 房客。这两种用户类型共享一些通用属性。例如,它们都有名字和个人资料图片。它们还有一些特定于其角色的属性:只有 房东 有个人资料简介和房源,而只有 房客 有预订。
为了将此业务逻辑实现到我们的GraphQL 架构中,我们可以使用接口。
在本课程中,我们将
- 了解如何在 GraphQL 架构中实现接口类型。
- 了解如何解析接口类型
什么是接口?
一个 接口是抽象类型,它定义了一组通用的 字段,然后,任意数量的 对象类型都可以包括这些字段。
接口通常用于表示具有某些共享行为的不同类型之间的一种重要关系。例如,Airlock 架构为房东
和 房客
定义了不同的类型,但这两種類型之间的共享属性在 用户
接口中被捕获。
当对象类型使用接口时,称为实现对象类型。我们还说该类型“实现了”接口。例如,Host
类型实现了User
接口。
一个接口规定了契约,其指定的实现类型必须遵循。换句话说,实现对象类型 必须包括该接口上定义的所有字段。因此,我们建议创建有意义的接口,以避免随着图形的发展进行不必要的模式维护。
实现对象类型还可以定义任意数量的其他字段,这些不是接口的一部分。
定义一个接口
在GraphQL 模式中,我们使用interface
关键字定义一个接口,然后是接口的名称。在大括号之后,我们定义字段,就像我们以前在其他模式中使用模式定义语言所做的那样。
这是 Airlock 的User
接口的样子:
"Represents an Airlock user's common properties"interface User {id: ID!"The user's first and last name"name: String!"The user's profile photo URL"profilePicture: String!}
任何实现我们的User
接口的类型都必须使用这些确切的返回类型(包括可空性)定义这些确切字段。它还可以定义任意数量的其他字段。(稍后详细介绍...)
实现接口
定义一个接口后,它可以由架构中的其他类型实现。
要定义一个实现的 对象类型,我们首先编写关键字 type
,然后跟上类型名称。然后,我们添加关键字 implements
,然后跟上接口的名称。接下来,我们将接口定义的所有 域 添加到类型定义。
Airlock 定义 Host
和 Guest
类型的过程如下,它们都实现了上一部分的 User
接口:
type Host implements User {id: ID!"The user's first and last name"name: String!"The user's profile photo URL"profilePicture: String!"The host's profile bio description, will be shown in the listing"profileDescription: String!}type Guest implements User {id: ID!"The user's first and last name"name: String!"The user's profile photo URL"profilePicture: String!"The reservations guest has"bookings: [Booking]!}
请注意, Host
和 Guest
类型都有 不属于 User
接口的其他类型(Host.profileDescription
和 Guest.bookings
)。
返回一个接口
接口还可以用作架构中的返回类型。在 Airlock 架构中, Review.author
域 是返回 User
类型的域的完美示例。评论作者可以是任何用户,无论是房东还是客人。
type Review {# ... other fields"User that wrote the review"author: User!}
注意: Query.me
域 也是一个很好的示例。我们将在下一课深入了解该域。
解析一个接口
A 字段返回一个接口,它可以返回已实现该接口的任何对象类型。但这就带来了一个问题:对于任何给定的操作,我们如何知道字段将返回哪种实现类型?
例如,在 Airlock 中Review.author
字段可能会返回一个Host
对象,也可能返回一个Guest
对象。我们如何知道返回的对象是房东还是客人?
为了处理此事,我们需要定义一个特殊解析程序函数,名为__resolveType
。
解析程序 __resolveType
函数__resolveType
负责确定正要返回哪种实现对象类型。它返回一个字符串,其中包含相对应对象类型的名称。
例如,User
接口的__resolveType
函数应返回“Host”或“Guest”之一,因为它们是在架构中定义的两种实现对象类型。
与我们的其他解析程序函数不同,__resolveType
接收三个可选 参数: obj
、context
和info
。
- 第一个 参数,
object
是 解析器为 字段返回的接口返回的对象。 - 最后两个 参数与我们 在 Lift-off II 中说明的一样。
__resolveType(obj, context, info) {// logic to determine which type to return goes here}
确定 哪一种 对象类型返回取决于应用程序!在 Airlock 的示例中,accounts
数据库中的每个用户都有一个 role
属性,该属性设置为 “Host”或 “Guest”。这非常适合使用,因为实现此接口的两种类型也是 Host
或 Guest
。
User: {__resolveType(user) {return user.role; // returns "Host" or "Guest"},},
注意:由于我们不会使用最后两个 参数(context
或 info
),我们将它们从函数调用中省略。我们还将第一个 参数 obj
重命名为 user
,以更好地说明什么是对象。
在 GraphOS Studio 中测试
让我们使用 GraphOS Studio来试用 查询,该查询解析了接口。
在网络浏览器中,在 GraphOS Studio 沙盒中打开
https://127.0.0.1:4000
。在 操作选项卡中,让我们开始构建我们的 查询。首先,我们将添加
featuredListings
域及其reviews
子域。然后,我们可以添加author
域,我们知道它将解析为User
接口。query GetFeaturedListings {featuredListings {reviews {author {# TODO!}}}}
当我们添加 author
域到我们的 查询时,请注意 文档面板如何更新以向我们显示此接口的可能 实现。在这里,可以看到封装在 User
接口中的共享 域以及特定于 Host
或 Guest
的域。
现在,我们只看看 域,使其在 User
接口中 共享两种实现类型: id
、 name
和 profilePicture
。
添加
id
、name
和profilePicture
字段 到 GraphOS 工作室 中的 查询。最终查询应如下所示:query GetFeaturedListings {featuredListings {reviews {author {idnameprofilePicture}}}}当我们运行 查询 时,我们看到我们的数据正在返回。太棒了!响应应如下面的对象所示。
在 Airlock 代码库中查看
查看在 Airlock 代码库中定义的接口。你可以在 server/schema.graphql
文件中找到它们。
练习
使用以下模式完成下面的代码挑战
type Query {availableBooks: [Book]borrowedBooks(userId: ID!): [Book]}interface Book {isbn: ID!title: String!genre: String!}type PictureBook implements Book {isbn: ID!title: String!genre: String!numberOfPictures: IntisInColor: Boolean}type YoungAdultNovel implements Book {isbn: ID!title: String!genre: String!wordCount: IntnumberOfChapters: Int}
为 Book
接口编写 __resolveType
解析器。若要确定 Book
的类型,可以使用该书的 hasPictures
属性。 PictureBook
类型的 hasPictures
属性被设为 true
,而 YoungAdultNovel
类型的 hasPictures
属性未被设置为 true
。
要点
- 接口定义了一组通用 字段,任何数量的 对象类型都必须包括在内。
- 我们建议创建有意义的接口来避免不必要的模式维护,随着 图形的演变。
接下来
到目前为止,我们已经了解了如何实现接口以及解决字段所有实现类型共有的。
在下一课中,我们将了解如何查询 字段只属于一个实现类型而不属于另一个实现类型的。为此,我们需要了解查询片段。