私たちは、長いローディング画面を待っているとき、動かないページになってしまう不機嫌に耐えることがあります。どこでもローディングスピナーが見られますが、何も進み続けないようです。私がより明確に描いた画を見てください:
これは通常、ウェブサイトがページに着いたときにすべての必要なデータを取得しようとしているために発生します。APIのリクエストが処理中であるか、複数のAPIが順番にデータを取得していて、ページの読み込みに遅延が生じる可能性があります。
結果としては、悪影響が出ます。あなたは「どうしてそれほど大きな会社でユーザー体験を重視していないのですか?残念です。」と思うかもしれません。そのため、ユーザーはサイトを離れることが多く、重要な指標や、リvenueに影響を与える可能性があります。
しかし、これらの重いページのデータを事前に取得することができるとしたら、ユーザーがページに着いたとき、すぐに操作できるようになるでしょう。
ここでは、先行読み込みという概念が重要になり、このブログ投稿ではそれについて深入りします。さっきの话を续けて、始めましょう。
目次
-
問題の理解
预fetchingを解決策として
以下は文法とスペルを修正した版です。
上記の問題において、私たちが求めるのは、特定のページのデータをウェブサイト上に载せる前に取得することで、ユーザーがページ载入時にデータを取得する必要がないようにすることです。これは预fetchingと呼ばれます。技術的な定義は以下の通りです:
必要なデータを事前に取得する手段で、メインコンポーネントがデータを待つ必要がなくなり、体験を改善します。
これは、ユーザー体験を改善し、顧客があなたのウェブサイトに対する信頼を高めることができます。
预fetchingは、標準プロセスよりもよりユーザー中心の解決策で、簡単で洗練されています。预fetchingを実装するためには、ウェブサイト上でのユーザーの行動を理解する必要があります。つまり、最も訪れられるページや、小さなインタラクションでデータを取得するコンポーネントについてです。
このようなシーンの分析の後、先読みをそれらに適用することは合理的です。しかし、開発者として、この概念の使用には注意が必要です。すすんで先読みを行うすぎると、サイトの速度を下げる可能性があります。なぜなら、主要なページのデータの取得をブロックするために、将来的なシーンにたくさんのデータを取得しようとするからです。
先読みがユーザー体験を改善する方法
次のようなシーンに先読みが有益であることを見てみましょう。
-
例えば、あなたの landing page から最も訪れられるリンクにデータ/ページを早期に読み込むこと。たとえば、「お問い合わせ」リンクを考えてみてください。これが用户がよく查看するリンクであり、読み込まれるときに多くのデータを含んでいると仮定します。contack us ページでデータを読み込む代わりに、首页でデータを取得するだけです。これにより、Contact Us ページでデータを待つ必要がなくなります。先読みを行う方法についてはここを参照してください。
-
後のページのテーブルデータの先読み。
-
親コンポーネントからデータを取得し、子コンポーネントに読み込む。
-
ポップアップで表示する必要のあるデータのプリフェッチ。
これらは、アプリケーションでプリフェッチを実現するいくつかの方法であり、ユーザー体験の改善を助けることです。
本日のブログ記事では、最後のシーン:“ポップアップで表示する必要のあるデータのプリフェッチ”について話します。これは、プリフェッチを有益にする classic example であり、ユーザーにより滑らかな体験を提供します。
問題の理解
問題を定義してみましょう。以下のようなシーンを想象してください:
-
特定の情報を表示するコンポーネントをあなたにあります。
-
このコンポーネントの中には、それをホバーしたときに別のポップアップ/ツールチップを表示する要素があります。
-
ポップアップはデータを読み込んだときに取得します。
ここで、ユーザーが要素をホバーしたときにデータを取得し、ポップアップに表示するのを待つ必要があります。この待ち時間に、スケルトンローダーを見ることができます。
このシーンは以下のように見えるでしょう:
画像上にマウスを乗せた際に用户が待つ必要がある時間が長いことはとても不快です。
この問題を解決するために、以下の2つの解決策があります。これらは、あなたの需要に応じて解決策を最適化するのに役立ちます。
解決策 #1: 親コンポーネントでデータの先読み
この解決策は、Martin Fowlerのブログ投稿から得られました。これにより、ポップアップが表示される前にデータを取得することができます。
ポップアップは、画像にマウスが乗せたときに表示されます。親コンポーネントにマウスが入ったときにデータを取得することができます。実際のコンポーネント、画像にマウスが乗る前に、ポップアップのデータが取得され、ポップアップコンポーネントに渡されます。
この解決策は、ローディング状態を完全に除去するわけではありませんが、ローディング状態を見る可能性を显著に低下させます。
解決策 #2: ページ読み込み時にデータの先読み
この解決策は、x.comに触発されました。ここで、ポップアップコンポーネントには、主要なページ読み込み時に一部のデータを取得し、余剩のデータをコンポーネントのマウント時に取得します。
上記のビデオを見ると、用户のプロフィール詳細はポップアップで表示されます。よく見ると、フォロワーに関連する詳細は後で取得されます。
この技術は、ポップアップに表示する大量のデータを持っている場合、それらの取得がポップアップのマウントやメインページの読み込み時にはコストのかかる場合に非常に効果的です。
より良い解決策は、メインページ上で必要なデータの一部だけを読み込むことで、コンポーネントがマウントされた際に残りのデータを読み込むようにすることです。
私たちの例では、ポップアップのデータを画像の親要素にカーソルが入った際に取得しました。今、ポップアップのデータを読み込んだ後に追加の詳細を取得する必要があると考えてみてください。すると、上記のx.comの方法に基づいて、ポップアップの読み込み時に追加のデータを取得することができます。以下は、その結果です。
ここで、以下のことを行います。
-
画像の親コンポーネントにカーソルが入った際に、ポップアップを表示するために必要な主要データの取得を行います。
-
これにより、主要データの取得を十分に時間を取ることができます。
-
ポップアップの読み込み時に、次のデータ(アルバム数)を取得します。ユーザーが名前やE-mailのデータを読んでいる間に、次のデータが準備されているからです。
このように、画面上の読み込み中の表示を最小限に抑える小さな smart な改修を行うことができます 😊。
ReactでPrefetchingを実装する方法
この節では、上記の预fetchingの例アプリを実装する方法に簡単に触れることにします。
プロジェクト設定
预fetchingアプリを作成するために、以下の手順に従います。
ここでは、vitejs(私が使用しているもの)またはcreate-react-appを使用してアプリを作成できます。以下のコマンドをターミナルに貼り付けます。
yarn create vite prefetch-example --template react-ts
アプリが作成された後、prefetch-exampleフォルダをVS Codeで開いたときに、以下のフォルダ構造を持っているはずです。
次に、このアプリに追加するコンポーネントについて考えていきましょう。
コンポーネント
この例では、3つのコンポーネントを使用します。
-
PopoverExample
-
UserProfile
-
UserProfileWithFetching
PopoverExample
コンポーネント
まず最初のコンポーネント、PopoverExample
に取り組みましょう。このコンポーネントは、画像のアバターとそれを右側に表示するテキストを表示します。これは以下のようになります。
このコンポーネントの目的は、実際の場面に似た例を提供することです。このコンポーネント内の画像は、ホバーしたときにポップアップコンポーネントを表示するように設定されます。
コンポーネントのコードは以下の通りです。
import { useState } from "react";
import { useFloating, useHover, useInteractions } from "@floating-ui/react";
import ContentLoader from "react-content-loader";
import UserProfile from "./UserProfile";
import UserProfileWithFetching from "./UserProfileWithFetching";
export const MyLoader = () => (
<ContentLoader
speed={2}
width={340}
height={84}
viewBox="0 0 340 84"
backgroundColor="#d1d1d1"
foregroundColor="#fafafa"
>
<rect x="0" y="0" rx="3" ry="3" width="67" height="11" />
<rect x="76" y="0" rx="3" ry="3" width="140" height="11" />
<rect x="127" y="48" rx="3" ry="3" width="53" height="11" />
<rect x="187" y="48" rx="3" ry="3" width="72" height="11" />
<rect x="18" y="48" rx="3" ry="3" width="100" height="11" />
<rect x="0" y="71" rx="3" ry="3" width="37" height="11" />
<rect x="18" y="23" rx="3" ry="3" width="140" height="11" />
<rect x="166" y="23" rx="3" ry="3" width="173" height="11" />
</ContentLoader>
);
export default function PopoverExample() {
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState({});
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "top",
});
const hover = useHover(context);
const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
const handleMouseEnter = () => {
if (Object.keys(data).length === 0) {
setIsLoading(true);
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((resp) => resp.json())
.then((data) => {
setData(data);
setIsLoading(false);
});
}
};
return (
<div
id="hover-example"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
textAlign: "left",
}}
onMouseEnter={handleMouseEnter}
>
<span
style={{
padding: "1rem",
}}
>
<img
ref={refs.setReference}
{...getReferenceProps()}
style={{
borderRadius: "50%",
}}
src="https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_5.png"
/>
</span>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text ever
since the 1500s, when an unknown printer took a galley of type and
scrambled it to make a type specimen book. It has survived not only five
centuries, but also the leap into electronic typesetting, remaining
essentially unchanged. It was popularised in the 1960s with the release
of Letraset sheets containing Lorem Ipsum passages, and more recently
with desktop publishing software like Aldus PageMaker including versions
of Lorem Ipsum.
</p>
{isOpen && (
<div
className="floating"
ref={refs.setFloating}
style={{
...floatingStyles,
backgroundColor: "white",
color: "black",
padding: "1rem",
fontSize: "1rem",
}}
{...getFloatingProps()}
>
{isLoading ? (
<MyLoader />
) : (
<UserProfile hasAdditionalDetails {...data} />
)}
{/* <UserProfileWithFetching /> */}
</div>
)}
</div>
);
}
ここにはいくつかのことが起こっています。ステップを踏まえて説明しましょう。
-
親の
div
要素に名前hover-example
を付け、画像といくつかのテキストを含んでいます。 -
次に、画像上にマウスをかざしたときに開かれる実際のポップアップコンポーネントとして、クラス名
floating
のdiv
を条件付きでレンダリングしました。- 私たちは
floating-ui
ライブラリを利用し、その基本的なマウスオーバーの例を用いてポップアップのマウスオーバー効果を実現しました。
- 私たちは
-
ポップアップの中で、
UserProfile
とスケルトンローダーを条件付きで読み込んだりしました。このローダーは、ユーザーのプロフィールのデータを取得している間に表示されます。これについて後で説明します。 -
私たちは、
MyLoader
コンポーネントに react-content-loader ライブラリを使用しました。このライブラリには、ローダーを作成するためのウェブサイトもありますが、ここでご覧ください。
UserProfile
コンポーネント
今は、Popover
の例を定義したので、次に UserProfile
コンポーネントの詳細に取り組むべきです。
このコンポーネントは、ポップアップコンポーネントの内部に表示されます。このコンポーネントの目的は、name
、email
、phone
、website
の詳細を読み込むことです。これらのデータは JSON placeholder APIから取得されます。
UserProfile
コンポーネントはプレゼンテーション的なコンポーネントとして機能するだけであることを保証する必要があります。つまり、このコンポーネント内には明示的な取得ロジックが存在していないことです。
このコンポーネントについては、データの取得は親コンポーネント、すなわちPopoverExample
コンポーネントから行われます。このコンポーネントでは、マウスがこのコンポーネントに入りたとき(mouseenter
イベント)からデータの取得を開始します。これは先程話した解決策の第1の方法です。
これにより、ユーザーが画像をホバーしたまでにデータの取得を完了する時間が十分に与えられます。以下がコードです。
import { useEffect, useState } from "react";
import ContentLoader from "react-content-loader";
const MyLoader = () => (
<ContentLoader
speed={2}
viewBox="0 0 476 124"
backgroundColor="#d1d1d1"
foregroundColor="#fafafa"
>
<rect x="4" y="43" rx="0" ry="0" width="98" height="30" />
</ContentLoader>
);
export default function UserProfile(props: Record<string, string | boolean>) {
const { name, email, phone, website, hasAdditionalDetails } = props;
const [isLoading, setIsLoading] = useState(false);
const [additionalData, setAdditionalData] = useState(0);
useEffect(() => {
if (hasAdditionalDetails) {
setIsLoading(true);
fetch("https://jsonplaceholder.typicode.com/albums")
.then((resp) => resp.json())
.then((data: Array<unknown>) => {
const albumCount = data.reduce((acc, curr) => {
if (curr.userId === 1) acc += 1;
return acc;
}, 0);
setAdditionalData(albumCount);
})
.finally(() => {
setIsLoading(false);
});
}
}, [hasAdditionalDetails]);
return (
<div id="user-profile">
<div id="user-name">name: {name}</div>
<div id="user-email">email: {email}</div>
<div id="user-phone">phone: {phone}</div>
<div id="user-website">website: {website}</div>
{hasAdditionalDetails && (
<>
{isLoading ? (
<MyLoader />
) : (
<div id="user-albums">Album Count: {additionalData}</div>
)}
</>
)}
</div>
);
}
このコンポーネントではhasAdditionalDetails
プロパティを使用しています。このprop
は、コンポーネントがマウントされたときに追加のデータを読み込む目的で使用されます。これは先程提到した解決策の第2の方法を示しています。
UserProfileWithFetching
コンポーネント
このコンポーネントはUserProfile
コンポーネントと非常に似ています。このコンポーネントには、コンポーネントの読み込み時にデータを取得するロジックしか含まれていません。このコンポーネントの目的は、プリフェッチング技術を使用せずの一般的な解決策を示すことです。
したがって、このコンポーネントは常にコンポーネントがマウントされたときにデータを読み込むことで、スケルトンローダーを表示します。
以下がコードです。
import { useEffect, useState } from "react";
import { MyLoader } from "./PopoverExample";
export default function UserProfileWithFetching() {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<Record<string, string>>({});
useEffect(() => {
setIsLoading(true);
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((resp) => resp.json())
.then((data) => {
setData(data);
setIsLoading(false);
});
}, []);
if (isLoading) return <MyLoader />;
return (
<div id="user-profile">
<div id="user-name">name: {data.name}</div>
<div id="user-email">email: {data.email}</div>
<div id="user-phone">phone: {data.phone}</div>
<div id="user-website">website: {data.website}</div>
</div>
);
}
このアプリのすべてのコードはここに見つかります。
プリフェッチングすすぎることもスラownessを引き起こすことがあります。
一言の忠告、プリフェッチングすすぎることは良くありません。理由は以下です。
-
アプリの性能を下げるかもしれません。
-
ストラテジックに前方取得を適用しない場合、ユーザーの経験が劣化する可能性があります。
前方取得は、ユーザーの行動を理解したときにのみ適用する必要があります。つまり、指標に基づいてユーザーの移動を予測でき、彼らがすぐにあるページを訪れるか否かを知ることができる場合であれば、前方取得は良いアイデアです。
したがって、常に戦略的に前方取得を適用してください。
概要
これこそすべてです!ブログ記事をお気に入りにしていただけたと思います。このブログ記事で、前方取得の実装がwebアプリケーションのスピードと対応性を大幅に向上させ、ユーザーの満足度を向上させることを学びました。
さらなる読み物については、以下の記事を参照してください:
Source:
https://www.freecodecamp.org/news/boost-web-performance-with-prefetching/