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

查詢基礎

本節內容

  • 我們將採用一個顯示硬式編碼佔位符資料的 React 元件,並修改它,使其使用 GraphQL 查詢來提取資料。
  • 我們將學習如何使用 Relay 從您的 GraphQL 產生的 TypeScript 類型,以確保類型安全。

使用 Relay,您可以使用 GraphQL 查詢來提取資料。查詢指定您的應用程式要提取的 GraphQL 圖表的一部分,從某個根節點開始,並從一個節點遍歷到另一個節點,以樹狀結構檢索一組特定的資料。

A query selects a particular subgraph

現在,我們的範例應用程式沒有提取任何資料,它只呈現一些硬式編碼在 React 元件中的佔位符資料。讓我們修改它,使其使用 Relay 來提取一些資料。

開啟名為 Newsfeed.tsx 的檔案。(教學中的所有元件都在 src/components 中。)在其中,您應該會看到一個 <Newsfeed> 元件,其中的資料是硬式編碼的。

export default function Newsfeed() {
const story = {
title: "Placeholder Story",
summary:
"Placeholder data, to be replaced with data fetched via GraphQL",
poster: {
name: "Placeholder Person",
profilePicture: {
url: "/assets/cat_avatar.png",
},
},
thumbnail: {
url: "/assets/placeholder.jpeg",
},
};
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}

我們將使用從伺服器提取的資料來取代這個佔位符資料。首先,我們需要定義一個 GraphQL 查詢。在 Newsfeed 元件上方新增以下宣告:

import { graphql } from 'relay-runtime';

const NewsfeedQuery = graphql`
query NewsfeedQuery {
topStory {
title
summary
poster {
name
profilePicture {
url
}
}
thumbnail {
url
}
}
}
`;

讓我們分解一下:

  • 若要將 GraphQL 嵌入 JavaScript 中,我們使用 graphql`` 標籤標記的字串文字。這個標籤允許 Relay 編譯器在 JavaScript 程式碼庫中尋找並編譯 GraphQL。
  • 我們的 GraphQL 字串包含一個 查詢宣告,其中包含關鍵字 query,然後是查詢名稱。請注意,查詢名稱必須以模組名稱開頭(在此案例中為 Newsfeed)。
  • 查詢宣告內有欄位,指定要查詢哪些資訊
    • 某些欄位是純量欄位,會擷取字串、數字或其他資訊單位。
    • 其他欄位是邊緣,可讓我們從圖表中的一個節點遍歷到另一個節點。當欄位是邊緣時,它後面會跟著另一個區塊 { },其中包含邊緣另一端的節點的欄位。在此,poster 欄位是一個從 Story 連接到發文者的 Person 的邊緣。一旦我們遍歷到 Person,我們就可以包含有關 Person 的欄位,例如他們的 name

這說明了此查詢所要求的圖表部分。

Parts of the GraphQL query

現在我們已經定義了查詢,我們需要做兩件事。

  1. 執行 Relay 編譯器,以便它知道新的 Graphql 查詢。[npm run relay.]
  2. 修改我們的 React 元件以提取它,並使用伺服器傳回的資料。

如果您開啟 package.json,您會發現指令碼 relay 已連結至執行 Relay 編譯器。這就是 npm run relay 的用途。一旦編譯器成功更新/產生新的已編譯查詢,您將能夠在 src/components/ 底下的 generated 資料夾中找到它,名稱為 NewsfeedQuery.graphql.ts。此專案隨附預先計算的片段,因此除非您執行此步驟,否則您將無法獲得所需的結果。

接下來,回到 Newsfeed 元件,並從刪除佔位符資料開始。然後,將其取代為以下內容:

import { useLazyLoadQuery } from "react-relay";

export default function Newsfeed({}) {
const data = useLazyLoadQuery(
NewsfeedQuery,
{},
);
const story = data.topStory;
// As before:
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}

useLazyLoadQuery 掛鉤會提取並傳回資料。它會採用兩個引數:

  • 我們之前定義的 GraphQL 查詢
  • 與查詢一起傳遞至伺服器的變數。此查詢未宣告任何變數,因此它是空物件。

useLazyLoadQuery 傳回的物件具有與查詢相同的形狀。例如,如果以 JSON 格式列印,它可能看起來像這樣:

{
topStory: {
title: "Local Yak Named Yak of the Year",
summary: "The annual Yak of the Year awards ceremony ...",
poster: {
name: "Baller Bovine Board",
profilePicture: {
url: '/images/baller_bovine_board.jpg',
},
},
thumbnail: {
url: '/images/max_the_yak.jpg',
}
}
}

請注意,GraphQL 查詢選取的每個欄位都會對應到 JSON 回應中的屬性。

此時,您應該會看到從伺服器提取的故事。

Screenshot

注意

伺服器的回應會被人為減慢,以使載入狀態可以被感知,這在我們為應用程式新增更多互動性時會派上用場。如果您想移除延遲,請開啟 server/index.js 並移除對 sleep() 的呼叫。

useLazyLoadQuery 掛鉤會在第一次呈現元件時提取資料。Relay 也提供在應用程式載入之前預先提取資料的 API — 這些將在稍後介紹。在任何情況下,Relay 都會使用 Suspense 來顯示載入指示器,直到資料可用為止。

這是 Relay 最基本的形式:在呈現元件時提取 GraphQL 查詢的結果。隨著教學的進展,我們將了解 Relay 的功能如何組合在一起,使您的應用程式更易於維護 — 從了解 Relay 如何產生對應於每個查詢的 TypeScript 類型開始。

深入探討:用於資料載入的 Suspense

Suspense 是 React 中的一個新 API,可讓 React 在載入資料時等待,然後再呈現需要該資料的元件。當元件需要在呈現之前載入資料時,React 會顯示載入指示器。您可以使用名為 Suspense 的特殊元件來控制載入指示器的位置和樣式。

現在,App.tsx 內有一個 Suspense 元件,它會在 useLazyLoadQuery 載入資料時顯示旋轉器。

當我們為應用程式新增更多互動性時,我們將在後面的章節中更詳細地介紹 Suspense。

深入探討:查詢是靜態的

Relay 應用程式中的所有 GraphQL 字串都會由 Relay 編譯器預先處理,並從產生的套件程式碼中移除。這表示您無法在執行階段建構 GraphQL 查詢 — 它們必須是靜態字串文字,以便它們在編譯時已知。但它帶來了主要優勢。

首先,它允許 Relay 為查詢的結果產生類型定義,使您的程式碼更具類型安全性。

其次,Relay 會將 GraphQL 字串文字取代為告訴 Relay 要做什麼的物件。這比直接在執行階段使用 GraphQL 字串快得多。

此外,Relay 的編譯器可以設定為在您建置應用程式時將查詢儲存到伺服器,以便在執行階段,用戶端只需要傳送查詢 ID 而不是查詢本身。這可節省套件大小和網路頻寬,並可以防止攻擊者撰寫惡意查詢,因為只需要您建置應用程式時可用的查詢即可。

因此,當您的程式中有一個 GraphQL 標記的字串文字時...

const MyQuery = graphql`
query MyQuery {
viewer {
name
}
}
`;

... JavaScript 變數 MyQuery 實際上會被指派給一個看起來像這樣的物件:

const MyQuery = {
kind: "query",
selections: [
{
name: "viewer",
kind: "LinkedField",
selections: [
name: "name",
kind: "ScalarField",
],
}
]
};

以及各種其他屬性和資訊。這些資料結構經過精心設計,可讓 JIT 非常快速地執行 Relay 的酬載處理程式碼。如果您好奇,可以使用Relay 編譯器瀏覽器來玩玩看。


Relay 和類型系統

您可能會注意到,TypeScript 會報告我們撰寫的此程式碼有錯誤:

const story = data.topStory;
^^^^^^^^
Property 'topStory' does not exist on type 'unknown'

若要修正此問題,我們需要使用 Relay 產生的類型來註解對 useLazyLoadQuery 的呼叫。這樣,TypeScript 就會根據我們在查詢中選取的欄位知道 data 應該具有什麼類型。新增以下內容:

import type {NewsfeedQuery as NewsfeedQueryType} from './__generated__/NewsfeedQuery.graphql';

function Newsfeed({}) {
const data = useLazyLoadQuery
<NewsfeedQueryType>
(NewsfeedQuery, {});
...
}

如果我們查看 __generated__/NewsfeedQuery.graphql 內部,我們會看到以下類型定義 — 透過我們剛新增的註解,TypeScript 知道 data 應該具有此類型:

export type NewsfeedQuery$data = {
readonly topStory: {
readonly poster: {
readonly name: string | null;
readonly profilePicture: {
readonly url: string;
} | null;
};
readonly summary: string | null;
readonly thumbnail: {
readonly url: string;
} | null;
readonly title: string;
} | null;
};

Relay 編譯器會針對您應用程式中 graphql`` 文字內的每個 GraphQL 產生對應的 TypeScript 類型。只要 npm run dev 正在執行,Relay 編譯器就會在您儲存其中一個 JavaScript 原始檔時自動重新產生這些檔案,因此您不需要重新整理任何內容來使其保持最新狀態。

使用 Relay 產生的類型可以讓你的應用程式更安全且更容易維護。除了 TypeScript 之外,如果你想使用 Flow 類型系統,Relay 也支援。當使用 Flow 時,useLazyLoadQuery 上的額外註解是不需要的,因為 Flow 可以直接理解 graphql`` 標籤模板字串的內容。

我們將在本教程中重新探討類型。但接下來,我們將看看 Relay 如何在維護性方面提供幫助,這是一個更重要的方式。


總結

查詢是獲取 GraphQL 資料的基礎。我們已經了解了

  • 如何使用 graphql`` 標籤模板字串在我們的應用程式中定義 GraphQL 查詢。
  • 如何使用 useLazyLoadQuery hook 在元件渲染時獲取查詢結果。
  • 如何導入 Relay 產生的類型以確保類型安全。

在下一節中,我們將探討 Fragments,它是 Relay 最核心和獨特的方面之一。Fragments 讓每個元件可以定義自己的資料需求,同時保留向伺服器發出單個查詢的效能優勢。