Relay 的思維模式
Relay 的資料獲取方法深受我們使用 React 的經驗所啟發。特別是,React 將複雜的介面分解為可重複使用的元件,讓開發人員可以獨立地推理應用程式中離散的單元,並減少應用程式不同部分之間的耦合。更重要的是,這些元件是宣告式的:它們讓開發人員可以指定在給定狀態下 UI 應該長什麼樣子,而不必擔心如何顯示該 UI。與先前使用命令式指令來操作原生視圖(例如 DOM)的方法不同,React 使用 UI 描述來自動決定必要的指令。
讓我們看看一些產品使用案例,以了解我們如何將這些概念融入 Relay 中。我們假設您對 React 有基本的熟悉度。
為視圖獲取資料
根據我們的經驗,絕大多數產品都需要一種特定的行為:在顯示載入指示器的同時,獲取視圖階層的所有資料,然後在資料可用時渲染整個視圖。
一種解決方案是讓根元件宣告並獲取它及其所有子元件所需的資料。然而,這會引入耦合:對子元件的任何更改都需要更改任何可能渲染它的根元件!這種耦合可能意味著更高的錯誤機率,並減慢開發速度。
另一種合乎邏輯的方法是讓每個元件宣告並獲取它需要的資料。這聽起來很棒。但是,問題是元件可能會根據它接收到的資料渲染不同的子元件。因此,巢狀元件將無法渲染並開始獲取其資料,直到父元件的查詢完成。換句話說,這會強制資料獲取以階段方式進行:先渲染根元件並獲取它需要的資料,然後渲染其子元件並獲取它們的資料,依此類推,直到到達葉元件。渲染將需要多次緩慢的串行往返。
Relay 結合了這兩種方法的優點,允許元件指定它們需要的資料,但將這些需求合併為一個單一查詢,以獲取整個元件子樹的資料。換句話說,它會靜態地(即在您的應用程式執行之前;在您編寫程式碼時)確定整個視圖的需求!
這是藉助 GraphQL 實現的。函式元件使用一個或多個 GraphQL 片段來描述它們的資料需求。這些片段隨後會巢狀於其他片段中,最終巢狀於查詢中。當獲取此類查詢時,Relay 將為其及其所有巢狀片段發出單一網路請求。換句話說,Relay 執行期能夠為視圖所需的所有資料發出單一網路請求!
讓我們深入了解 Relay 如何實現此壯舉。
指定元件的資料需求
在 Relay 中,元件的資料需求是使用片段指定的。片段是 GraphQL 的具名程式碼片段,指定要從特定類型的物件中選擇哪些欄位。片段寫在 GraphQL 字面值內。例如,以下宣告一個包含片段的 GraphQL 字面值,該片段選擇作者的名稱和照片網址
// AuthorDetails.react.js
const authorDetailsFragment = graphql`
fragment AuthorDetails_author on Author {
name
photo {
url
}
}
`;
然後,透過在函式 React 元件中呼叫 useFragment(...)
hook 從儲存中讀取此資料。從中讀取此資料的實際作者由傳遞給 useFragment
的第二個參數決定。例如
// AuthorDetails.react.js
export default function AuthorDetails(props) {
const data = useFragment(authorDetailsFragment, props.author);
// ...
}
這個第二個參數 (props.author
) 是一個片段參考。片段參考是透過將片段散佈到另一個片段或查詢中獲得的。片段無法直接獲取。相反,所有片段最終都必須散佈(直接或傳遞地)到查詢中才能獲取資料。
讓我們看看這樣一個查詢。
查詢
為了獲取該資料,我們可能會宣告一個查詢,該查詢會如下散佈 AuthorDetails_author
// Story.react.js
const storyQuery = graphql`
query StoryQuery($storyID: ID!) {
story(id: $storyID) {
title
author {
...AuthorDetails_author
}
}
}
`;
現在,我們可以透過呼叫 const data = useLazyLoadQuery(storyQuery, {storyID})
來獲取查詢。此時,data.story.author
(如果存在;所有欄位預設為可為空值) 將是一個片段參考,我們可以將其傳遞給 AuthorDetails
。例如
// Story.react.js
function Story(props) {
const data = useLazyLoadQuery(storyQuery, props.storyId);
return (<>
<Heading>{data?.story.title}</Heading>
{data?.story?.author && <AuthorDetails author={data.story.author} />}
</>);
}
請注意這裡發生了什麼。我們發出了一個包含故事元件和 AuthorDetails
元件所需資料的單一網路請求!當該資料可用時,整個視圖可以在單一傳遞中渲染。
資料遮罩
在使用資料獲取的典型方法時,我們發現兩個元件具有隱式依賴關係是很常見的。例如,<Story />
可能會使用一些資料,而沒有直接確保資料已被獲取。此資料通常由系統的其他部分獲取,例如 <AuthorDetails />
。然後,當我們更改 <AuthorDetails />
並移除該資料獲取邏輯時,<Story />
會突然且無法解釋地中斷。這些類型的錯誤並不總是立即顯而易見,尤其是在較大團隊開發的較大型應用程式中。人工和自動測試只能提供有限的幫助:這正是框架可以更好地解決的系統性問題。
我們已經看到 Relay 確保視圖的資料一次全部獲取。但 Relay 還提供了另一個並不明顯的好處:資料遮罩。Relay 只允許元件存取它們在 GraphQL 片段中明確要求的資料,而不能存取其他任何資料。因此,如果一個元件查詢故事的 title
,而另一個元件查詢故事的 text
,則每個元件都只能看到它們要求的欄位。事實上,元件甚至看不到其子元件要求的資料:這也會破壞封裝。
Relay 還更進一步:它在 props
上使用不透明的識別碼來驗證我們是否在渲染元件之前明確獲取了該元件的資料。如果 <Story />
渲染 <AuthorDetails />
但忘記散佈其片段,Relay 將會警告說 <AuthorDetails />
的資料遺失。事實上,即使某個其他元件碰巧獲取了 <AuthorDetails />
所需的相同資料,Relay 也會發出警告。此警告告訴我們,儘管現在事情可能會正常運作,但它們很可能在稍後中斷。
結論
GraphQL 提供了一個強大的工具來建構高效、解耦的用戶端應用程式。Relay 在此功能之上建構,以提供宣告式資料獲取的框架。透過將獲取哪些資料與如何獲取資料分開,Relay 可協助開發人員建構預設情況下穩健、透明且高效能的應用程式。它是 React 所提倡的以元件為中心的思考方式的絕佳補充。雖然這些技術(React、Relay 和 GraphQL)本身都很強大,但它們的組合是一個 UI 平台,讓我們能夠快速行動並大規模發佈高品質的應用程式。
此頁面是否有用?
請透過以下方式協助我們讓網站變得更好 回答幾個快速問題.