GraphQL 類型、介面與多型
在本節中,我們將了解如何以不同的方式處理不同類型的節點。您可能會注意到範例應用程式中的一些新聞提要故事是由人發佈的,而另一些則是由組織發佈的。在此範例中,我們將透過撰寫一個片段來增強我們的懸浮卡,該片段會選取關於人的特定資訊以及關於組織的特定資訊。
我們已經暗示 GraphQL 節點不只是隨機的欄位集合,它們有類型。您的 GraphQL 結構描述定義了每個類型具有哪些欄位。例如,它可能會像這樣定義 Story
類型
type Story {
id: ID!
title: String
summary: String
createdAt: Date
poster: Actor
image: Image
}
這裡,一些欄位是純量(如 String
和 ID
)。另一些是在結構描述中其他地方定義的類型,如 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
}
這兩種類型都具有 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 {}
語法允許我們選取僅存在於實作介面的特定類型中的欄位。