GraphQL 类型、接口和多态
在本节中,我们将了解如何对不同类型的节点进行不同的处理。您可能注意到示例应用程序中的某些新闻提要故事是由个人发布的,而另一些则是由组织发布的。在本例中,我们将通过编写一个片段来增强我们的悬停卡,该片段将选择有关个人的人员特定信息和有关组织的组织特定信息。
我们已经暗示过 GraphQL 节点不仅仅是随机的字段集合 - 它们具有类型。您的 GraphQL 架构定义了每种类型具有哪些字段。例如,它可能像这样定义 Story
类型
type Story {
id: ID!
title: String
summary: String
createdAt: Date
poster: Actor
image: Image
}
这里,一些字段是标量(如 String
和 ID
)。其他的是架构中其他地方定义的类型,如 Image
- 这些字段是到这些特定类型节点的边。ID!
上的 !
表示该字段是不可为空的。在 GraphQL 中,字段通常是可为空的,不可为空是例外。
片段始终“处于”特定类型上。在上面的示例中,StoryFragment
是在 Story
上定义的。这意味着您只能将其扩展到查询中的预期出现 Story
节点的位置。这意味着片段只能选择存在于 Story
类型上的那些字段。
poster
字段使用的 Actor
类型尤其令人关注。此类型是接口。这意味着故事的poster
可以是人、页面、组织或任何满足作为“Actor”规范的任何其他类型的实体。
示例应用程序中的 GraphQL 架构将 Actor 定义如下
interface Actor {
name: String
profilePicture: Image
joined: DateTime
}
这恰好是我们在这里显示的信息。架构中有两种类型实现了 Actor,这意味着它们包含 Actor 中定义的所有字段并声明为 Actor
type Person implements Actor {
id: ID!
name: String
profilePicture: Image
joined: DateTime
email: String
location: Location
}
type Organization implements Actor {
id: ID!
name: String
profilePicture: Image
joined: DateTime
organizationKind: OrganizationKind
}
这两种类型都有 name
、profilePicture
和 joined
,因此它们都可以声明它们实现了 Actor,因此可以在架构和片段中需要 Actor 的任何地方使用。它们还有其他特定于每种特定类型的字段。
让我们看看如何通过扩展 PosterDetailsHovercardContentsBody
组件来显示 Person
的位置或 Organization
的组织类型来更深入地了解接口。这些字段仅存在于这些特定类型上,而不是在 Actor
接口上。
现在,如果您一直在关注,它应该有一个像这样定义的片段(在 PosterDetailsHovercardContents.tsx
中)
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
name
joined
profilePicture {
...ImageFragment
}
}
如果您尝试在此片段中添加类似 organizationKind
的字段,您将从 Relay 编译器收到错误
✖︎ The type `Actor` has no field organizationKind
这是因为当我们将片段定义为在接口上时,我们只能使用该接口中的字段。要使用实现接口的特定类型的字段,我们将使用类型细化来告诉 GraphQL 我们正在从该类型中选择字段
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
name
joined
profilePicture {
...ImageFragment
}
... on Organization {
organizationKind
}
}
现在就添加它。您还可以添加 Person
的类型细化
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
name
joined
profilePicture {
...ImageFragment
}
... on Organization {
organizationKind
}
... on Person {
location {
name
}
}
}
当您选择仅存在于实现接口的某些类型上的字段时,而您处理的节点是不同类型的,那么当您读出该字段的值时,您只需获得 null
。考虑到这一点,我们可以修改 PosterDetailsHovercardContentsBody
组件以显示人员的位置和组织的组织类型
import OrganizationKind from './OrganizationKind';
function PosterDetailsHovercardContentsBody({ poster }: Props): React.ReactElement {
const data = useFragment(PosterDetailsHovercardContentsBodyFragment, poster);
return (
<>
<Image image={data.profilePicture} width={128} height={128} className="posterHovercard__image" />
<div className="posterHovercard__name">{data.name}</div>
<ul className="posterHovercard__details">
<li>Joined <Timestamp time={poster.joined} /></li>
{data.location != null && (
<li>{data.location.name}</li>
)}
{data.organizationKind != null && (
<li><OrganizationKind kind={data.organizationKind} /></li>
)}
</ul>
</>
);
}
您现在应该看到人员的位置以及组织的组织类型
顺便说一句,我们现在可以理解为什么我们在前面的示例中使用 ... on Actor
。node
字段可以返回任何类型的节点,因为任何 ID 都可以在运行时给出。因此,它给我们的类型是 Node
,这是一个非常通用的接口,可以由任何具有 id
字段的东西实现。我们需要一个类型细化才能使用 Actor
接口中的字段。
在 GraphQL 规范和其他资料中,类型细化被称为内联片段。我们称之为“类型细化”,因为这种术语更具描述性且不易混淆。
如果您需要根据类型做一些完全不同的事情,您可以选择一个名为 __typename
的字段,该字段返回一个字符串,其中包含您获得的具体类型的名称(例如,"Person"
或 "Organization"
)。这是 GraphQL 的内置功能。
总结
... on Type {}
语法允许我们选择仅存在于实现接口的特定类型中的字段。