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

GraphQL 類型、介面與多型

在本節中,我們將了解如何以不同的方式處理不同類型的節點。您可能會注意到範例應用程式中的一些新聞提要故事是由人發佈的,而另一些則是由組織發佈的。在此範例中,我們將透過撰寫一個片段來增強我們的懸浮卡,該片段會選取關於人的特定資訊以及關於組織的特定資訊。


我們已經暗示 GraphQL 節點不只是隨機的欄位集合,它們有類型。您的 GraphQL 結構描述定義了每個類型具有哪些欄位。例如,它可能會像這樣定義 Story 類型

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

這裡,一些欄位是純量(如 StringID)。另一些是在結構描述中其他地方定義的類型,如 Image — 這些欄位是指向那些特定類型節點的邊緣。ID! 上的 ! 表示該欄位不可為 null。在 GraphQL 中,欄位通常可為 null,而不可為 null 則是一種例外。

片段總是「基於」特定的類型。在我們上面的範例中,StoryFragment 被定義為「基於 Story」。這表示您只能在查詢中預期 Story 節點的地方展開它。而且,這表示該片段只能選取 Story 類型上存在的那些欄位。

特別令人感興趣的是用於 poster 欄位的 Actor 類型。此類型是一種介面。這表示故事的 poster 可以是 Person、Page、Organization 或任何其他符合「Actor」規格的實體類型。

我們範例應用程式中的 GraphQL 結構描述定義了 Actor 如下

interface Actor {
name: String
profilePicture: Image
joined: DateTime
}

這並非巧合,這正是我們在此處顯示的資訊。結構描述中有兩種實作 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 {} 語法允許我們選取僅存在於實作介面的特定類型中的欄位。