Joe Savona氏がReact Confで、RelayとSuspenseを使ったユーザローディングエクスペリエンスの改善と、運用システムでSuspenseをデータフェッチに使用する中から見出したベストプラクティスについて詳しく説明した。
Savona氏の講演は、Suspenseが解決しようとする問題の再提起から始まった。アプリケーションでは時として、ユーザに提示する大量のデータのフェッチが必要になる。Reactにおいては、アプリケーションはコンポーネントの集合体として実装されているため、個々のコンポーネントをマウントする時にデータのフェッチが必要となる場合があり、その間はローディングインジケータが表示される。講演で"fetch on render"と呼んだこの単純なパターンは、その結果としてサーバと数回のラウンドトリップが行われ、滝のようなローディングインジケータが表示されることになる。Relayはこれらの問題のいくつかを緩和するため、5年前に導入された。
Reactでは、コンポーネントコードの中に必要なデータを記述する。
function Post(props) {
const data = useFragment(graphql`
fragment Post_fragment on Post {
title
body
}
` , props.post);
return (
<Title>{data.title}</Title>
<Body body={data.body}> />
);
}
Relayはアプリケーションコンポーネントツリー全体のデータ依存関係を収集して、可能であれば1回のラウンドトリップでサーバからフェッチする。この"fetch on render"パターンによって、前に示したようなスピナの嵐を回避することができる。
しかしこれでは、ページ全体を表示するためにすべてのデータの到着を待たなければならないので、ローディングエクスペリエンスとしてはまだ改善の余地がある。準備のできたコンポーネントをインクリメンタルに描画できれば、さらにはローディングインジケータの代わりに、レイアウト内でコンポーネントに割り当てられた全スペースを埋めるスペースホルダを表示することができれば、もっと優れたユーザエクスペリエンスを提供できる可能性がある。Savona氏は新しいfacebook.comを例に説明した。facebook.comではヘッダが最初に表示され、次いで左側のナビゲーション、ストーリ、ポストとフィードの順に描画される。ニュースフィードの方が(データを受信して)先に表示準備が完了したとしても、左側のナビゲーションが先に表示されるまで待機して、その後でニュースフィードを表示する方が望ましい。そうしないと、不快なレイアウト変更が短時間のうちに発生して、ユーザを困惑させるかも知れないからだ。
シングルページアプリケーションにおける新たなページのロード(トランジションなど)でも、次のページに必要なデータやコードを並行的にダウンロードしておいて、見かけ上の待ち時間が最小限になるようにページ間のトランジションを周到に用意しておくことで、スピードアップを図ることができる。facebook.comで使用されているパターンは"render as you fetch"と呼ばれるものだ。
これに関してSavona氏は、Facebookの新しいWebサイトでこのパターンを実装した方法について説明した。前述のように、コンポーネントでは、graphQLクエリをコンポーネントのコードと同じ場所におくという方法で、必要なデータを宣言的に記述している。レンダリングにリモートリソースのフェッチが必要なコンポーネントは、<Suspense fallback={<PlaceHolder/>}>/>
コンポーネント内にラップする。これによりReactは、必要なリソースをフェッチしている間、そのフォールバックを表示するようになる。ペンディングされているコンポーネント間のレンダリングのコーディネーションには<SuspenseList>
コンポーネントを使用する。
function Home(props) {
return (
<ErrorBoundary fallback={<ErrorMessage/>}>
<SuspenseList revealOrder="forwards">
<Suspense fallback={<ComposerFallback/>}>
<Composer />
</Suspense>
<Suspense fallback={<FeedFallback/>}>
<NewsFeed/>
</Suspense>
</SuspenseList>
</ErrorBoundary>
)
}
上記のコードでは、ニュースフィード(NewsFeed)とコンポーザ(Composer)のプレースホルダが最初に描画される。コンポーザはニュースフィードより前に、フェッチしたリソースの到着順とは無関係に描画される。
さらに<Suspense />
コンポーネントは、最初のコンポーネントの描画準備が完了するまで短時間待機することによって、見かけ上のローディングパフォーマンスをより最適化する。待機中に2番目のコンポーネントの描画準備ができれば、両方のコンポーネントを同時に描画することができるからだ。Reactのユーザ調査では、このパターンの肯定的な影響が確認できている。
パフォーマンスに対するユーザの認識は、絶対的な読み込み時間だけではない、ということがここから分かります。例えば、絶対的な起動時間が同じ2つのアプリを比較した場合、ユーザは一般的に、起動途中のロード状態やレイアウトの変化が少ない方が、ロードが高速であると感じるのです。
リソースフェッチがエラーになった場合の処理を行う<ErrorBoundary/>
コンポーネントにも注目すべきだ。
さらにFacebookでは、ページトランジションによって起動されるイベントのハンドラ内で、次のページで使用するリソース(コードとデータ)を識別する特別なAPIを使用している。これによって次ページのレンダリングが始まる前に、並行して、これらのリソースのフェッチがハンドラ内で開始されるようになる。
ここで紹介した以外のコードスニペットやアニメーションデモ、詳細な説明を含むSavona氏の講演のすべてが、ReactConfのサイトで公開されている。ReactConfはFacebookの主催する公式Reactイベントで、2019年はネバダ州ヘンダーソンで10月24、25日に開催されている。