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

渲染部分快取資料

在 Relay 中渲染快取資料時,可以執行部分渲染。我們將「部分渲染」定義為能夠立即渲染部分快取的查詢。也就是說,查詢的部分內容可能遺失,但查詢的部分內容可能已經快取。在這些情況下,我們希望能夠立即渲染已快取的查詢部分,而無需等待擷取整個查詢。

這在我們希望盡可能快速渲染畫面或頁面,並且我們知道該頁面的一些資料已經快取,因此可以跳過載入狀態的情況下非常有用。例如,以個人資料頁面為例:很有可能使用應用程式時,使用者的名稱已經被快取過,因此在造訪個人資料頁面時,如果使用者的名稱已快取,即使個人資料頁面的其餘資料尚未可用,我們也希望立即渲染。

片段作為部分渲染的邊界

為此,我們依賴片段元件的暫停能力(請參閱使用 Suspense 的載入狀態章節)。如果片段元件在渲染期間遺失任何其在本機宣告的資料,並且目前正在擷取資料,則該片段元件將會暫停。具體來說,它會暫停直到擷取其所需的資料,也就是直到擷取它所屬的查詢(其父查詢)。

讓我們用一個範例來說明這意味著什麼。假設我們有以下片段元件

/**
* UsernameComponent.react.js
*
* Fragment Component
*/

import type {UsernameComponent_user$key} from 'UsernameComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

type Props = {
user: UsernameComponent_user$key,
};

function UsernameComponent(props: Props) {
const user = useFragment(
graphql`
fragment UsernameComponent_user on User {
username
}
`,
props.user,
);
return (...);
}

module.exports = UsernameComponent;

我們有以下查詢元件,它查詢一些資料,並且還包含上面的片段

/**
* AppTabs.react.js
*
* Query Loader Component
*/

// ....

const onSelectHomeTab = () => {
loadHomeTabQuery({id: '4'}, {fetchPolicy: 'store-or-network'});
}

// ...

/**
* HomeTab.react.js
*
* Query Component
*/

const React = require('React');
const {graphql, usePreloadedQuery} = require('react-relay');

const UsernameComponent = require('./UsernameComponent.react');

function HomeTab(props: Props) {
const data = usePreloadedQuery(
graphql`
query HomeTabQuery($id: ID!) {
user(id: $id) {
name
...UsernameComponent_user
}
}
`,
props.queryRef,
);

return (
<>
<h1>{data.user?.name}</h1>
<UsernameComponent user={data.user} />
</>
);
}

假設當渲染此 HomeTab 元件時,我們先前已經擷取Username,其 {id: 4},並且它在本機快取在與我們目前 Relay 環境相關聯的 Relay 儲存區中。

如果我們嘗試使用允許重複使用本機快取資料的 fetchPolicy ('store-or-network''store-and-network') 渲染查詢,將會發生以下情況

  • 查詢將檢查是否遺失任何其在本機要求的資料。在這種情況下,沒有。具體來說,查詢只直接選取 name 欄位,而該欄位在儲存區中可用的。
    • 只有在本機宣告且遺失時,Relay 才會將資料視為遺失。換句話說,在片段展開中選取的資料不會影響外部查詢或片段是否被判斷為有遺失資料。
  • 鑑於查詢沒有任何遺失的資料,它會渲染,然後嘗試渲染子 UsernameComponent
  • UsernameComponent 嘗試渲染 UsernameComponent_user 片段時,Relay 會注意到渲染所需的一些資料遺失;具體來說,遺失了 username。此時,由於 UsernameComponent 有遺失的資料,因此它會暫停渲染直到網路請求完成。請注意,無論您選擇哪個 fetchPolicy,如果完整查詢(包括片段)的任何資料遺失,都將始終啟動網路請求。

此時,當 UsernameComponent 因遺失的 username 而暫停時,理想情況下,我們仍然應該能夠立即渲染 Username,因為它在本機快取中。但是,由於我們未使用 Suspense 元件來捕捉片段的暫停,因此暫停會向上冒泡,並且整個 App 元件將被暫停。

為了達到在 username 遺失時,也能夠在 name 可用時就渲染的預期效果,我們只需要將 UsernameComponent 包裝在 Suspense 中,以允許 App 的其他部分繼續渲染

/**
* HomeTab.react.js
*
* Query Component
*/

const React = require('React');
const {Suspense} = require('React');
const {graphql, usePreloadedQuery} = require('react-relay');

const UsernameComponent = require('./UsernameComponent.react');


function HomeTab() {
const data = usePreloadedQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
...UsernameComponent_user
}
}
`,
props.queryRef,
);

return (
<>
<h1>{data.user?.name}</h1>

{/*
Wrap the UserComponent in Suspense to allow other parts of the
App to be rendered even if the username is missing.
*/}
<Suspense fallback={<LoadingSpinner label="Fetching username" />}>
<UsernameComponent user={data.user} />
</Suspense>
</>
);
}

我們上面描述的流程對於巢狀片段(即包含在其他片段內的片段)的工作方式相同。這表示,如果渲染片段所需的資料在本機快取中,則片段元件將能夠渲染,無論其任何子片段或後代片段的資料是否遺失。如果子片段的資料遺失,我們可以將其包裝在 Suspense 元件中,以允許其他片段和應用程式的部分繼續渲染。

正如我們的動機範例中所述,這是可取的,因為它可以讓我們完全跳過載入狀態。更具體來說,能夠渲染部分可用的資料,可以讓我們渲染更接近最終渲染狀態的中間 UI 狀態。