跳至主要内容
版本:v18.0.0

GraphQL 类型、接口和多态

在本节中,我们将了解如何对不同类型的节点进行不同的处理。您可能注意到示例应用程序中的某些新闻提要故事是由个人发布的,而另一些则是由组织发布的。在本例中,我们将通过编写一个片段来增强我们的悬停卡,该片段将选择有关个人的人员特定信息和有关组织的组织特定信息。


我们已经暗示过 GraphQL 节点不仅仅是随机的字段集合 - 它们具有类型。您的 GraphQL 架构定义了每种类型具有哪些字段。例如,它可能像这样定义 Story 类型

type Story {
id: ID!
title: String
summary: String
createdAt: Date
poster: Actor
image: Image
}

这里,一些字段是标量(如 StringID)。其他的是架构中其他地方定义的类型,如 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
}

这两种类型都有 nameprofilePicturejoined,因此它们都可以声明它们实现了 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>
</>
);
}

您现在应该看到人员的位置以及组织的组织类型

An organization hovercard A person hovercard

顺便说一句,我们现在可以理解为什么我们在前面的示例中使用 ... on Actornode 字段可以返回任何类型的节点,因为任何 ID 都可以在运行时给出。因此,它给我们的类型是 Node,这是一个非常通用的接口,可以由任何具有 id 字段的东西实现。我们需要一个类型细化才能使用 Actor 接口中的字段。

注意

在 GraphQL 规范和其他资料中,类型细化被称为内联片段。我们称之为“类型细化”,因为这种术语更具描述性且不易混淆。

提示

如果您需要根据类型做一些完全不同的事情,您可以选择一个名为 __typename 的字段,该字段返回一个字符串,其中包含您获得的具体类型的名称(例如,"Person""Organization")。这是 GraphQL 的内置功能。

总结

... on Type {} 语法允许我们选择仅存在于实现接口的特定类型中的字段。