我們都經歷過在長時間的加載畫面後,發現自己卡在無反應的頁面上的挫折。你會看到 loading 標誌到处都是,但是似乎沒有任何進展。讓我為你描繪一個更清晰的图画:
這通常發生是因為當你點擊頁面時,網站正在嘗試盡可能快地获取所有必要的數據。這可能是一個 API 請求正在處理,或者多個 API 按序獲取數據,導致頁面加載延誤。
結果是什麼?用戶體驗不佳。你可能會想,“這樣的大型公司怎能把用戶體驗放在優先位置?這真是令人失望。” 因此,用戶經常離開該網站,影響關鍵指標,甚至可能影響收入。
但如果你想可以在用戶點擊頁面之前就獲取這些重量大頁面所需的數據,那麼當用戶登上該頁面時,他們可以立即與之互動,會怎樣?
這就是预取概念的由来,我們將在本篇博客文章中深入探讨這個問題。那麼,不再贅述,讓我們開始吧!
目錄
-
了解問題
預取作為解決方案
以下是僅修正語法和拼寫的修订版本:
對於上面的問題,我們希望的是在頁面加載到網站之前就去獲取給定頁面的數據,這樣用戶就不需要在頁面加載時獲取數據。這稱為預取。從技術角 checkoutdf7d3f186218a7b481e9f57885e52f22 度來看,它的定義如下:
它是一種事先獲取所需數據的方法,使得主要元件不需要等待數據,從而提升使用者體驗。
這可以提升使用者體驗,增強客戶對您網站的的信心。
預取是一種簡單且优雅的解決方案,比標準进程更加以人为本。要實作預取,我們需要了解網站上使用者的行為。那就是,最受訪問的頁面,或者在小型互動(如滑鼠懸停)時就要獲取數據的元件。
在分析了這些情境後, Apply prefetching to them 是合理的。然而,作為開發者,我們應該謹慎使用這個概念。過多的 prefetching 也可能會延缓你的網站,因為你正在為未來的情境嘗試抓取大量的數據,這可能會阻止主頁面數據的抓取。
预先加载如何提高用户体验
讓我們看一下幾個预先加载有益的情境:
-
例如,假設你有一个 “联系我们” 的鏈接。讓我們假設這是用戶最常查看的鏈接,且當它加載時含有大量數據。 vote_for_this_topic_191 rather than loading the data when the contact us page loads, you can simply fetch the data on the homepage so that you don’t have to wait at the Contact Us page for the data. You can read more about prefetching pages here.
-
為後面的頁面預先加載表數據。
-
從父组件中取數據並在子组件中加載。
-
預取需要在彈出提示中顯示的數據。
這些是在您的應用程序中實現預取的一些方式,以及它如何幫助提高用戶體驗。
在本文中,我們將討論最後一種情況:“在彈出提示中顯示的數據預取”。這是一個典型的例子,預取在此情況下可以產生益處,並為用戶提供更加平順的體驗。
理解問題
讓我在此定義問題。想像一下以下情況:
-
您有一個顯示特定信息的組件。
-
這個組件內有一個元素,當您鼠標懸停在它上面時,它會顯示另一個彈出提示/工具提示。
-
彈出提示在加載時取數據。
現在想像一下,用戶將鼠標懸停在元素上,需要等待數據被取回並在彈出提示中顯示。在這個等待過程中,他們看到骨架加載器。
這種情況看起來會是这样的:
真係 frustrating,每次使用者喺 image 上 hover 嘅時候要等嘅時間咁長:
為了解决個問題,有两个方案可以助你入手并根据需要 optimize 方案。
方案 1:在父组件中预取数据
这个方案灵感来自于 Martin Fowler 的博客文章。它允许你在弹出窗口出现之前就获取数据,而不是在组件加载时获取。
当鼠标悬停在弹出窗口上时,弹出窗口就会出现。我们可以在鼠标进入父组件时就获取数据。在实际的组件——图片——被悬停之前,我们就会有弹出窗口的数据,并将它传递给弹出窗口组件。
这个方案并没有完全移除加载状态,但它能显著降低看到加载状态的机会。
方案 2:在页面加载时预取数据
这个方案灵感来自于 x.com,在那里,对于弹出窗口组件,他们在主页面加载时部分获取数据,并在组件挂载时获取剩余的数据。
正如你從上面嘅视频所見,用戶嘅个人资料细节喺弹出窗口中查看。如果你仔细观察,与管理员相關嘅详情係後面fetch嘅。
這種技術在您必須在彈出視窗中顯示大量數據時非常高效,但從主頁面加載或彈出視窗掛载時加載這些數據可能會成本高昂。
更好的解決方案是主頁面上部分加載所需的數據,當組件掛載時再加載剩下的大部分數據。
在我們的示例中,當滑鼠進入圖片的父元素時,我們加載了彈出視窗的數據。現在想象一下,您需要在彈出視窗數據加載後再加載其他詳細信息。所以基於上述x.com的方法,我們可以在彈出視窗加載時再加載其他數據。這是它的結果:
在此,我們進行以下操作:
-
當滑鼠進入圖片的父元素時,我們獲取主要數據,這些數據僅需 render 彈出視窗。
-
這給我們足夠的時間來獲取主要數據。
-
在彈出視窗加載時,我們再加載另一份數據,這份數據是相簿數量。當用戶閱讀如姓名和電子郵件等數據時,我們將有下一份數據準備好供查看。
這樣,我們可以對代码進行微小的智能優化,以最小化屏幕上載入器的空瞪視效果😊。
如何使用 React 實現预取 Data
在本節中,我們將簡要地介紹如何實現上面提到的預取示例應用程序。
專案設定
要開始創建預取應用程序,請遵循以下步驟:
您可以使用vitejs(我使用的的就是這個)或create-react-app來創建您的應用程序。在您的終端機中貼上以下命令:
yarn create vite prefetch-example --template react-ts
應用程序創建完成後,您在VS Code中打開prefetch-example文件夾時,應該會有以下的文件結構。
現在讓我們深入了解我們將為此應用程序建造的组件。
组件
在這個示例中,我們將使用3個组件:
-
PopoverExample
-
UserProfile
-
UserProfileWithFetching
PopoverExample
组件
讓我們從第一個组件 PopoverExample
開始。這個组件顯示一個圖片頭像和一些文本在它的右側。它應該看起來像這樣:
這個组件的目的是作為類似於现实生活情境的示例。這個组件中的图片在滑鼠悬停時會加載一個彈出 component。
以下是元件的代碼:
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>
);
}
這裡發生了幾件事,讓我逐步解釋它們:
-
我們有一個名為
hover-example
的父div
,其中包含一幅圖像和一些文字。 -
接下來,我們條件式地渲染了一個帶有類名
floating
的div
。這是一個實際的彈出元件,當您在经济图形象時會打開。- 我們使用了
floating-ui
庫及其基本的悬停示例來實現彈出元件的懸停效果。
- 我們使用了
-
在彈出元件內,我們條件式地加載了
UserProfile
和骨架加載器。當我們正在獲取用戶个人资料的數據時,這個加載器會出現。稍後再详细了解。 -
我們在
MyLoader
元件中使用了react-content-loader庫。這個庫還有一個網站,可以帮助您創建加載器,您可以在此查看。
UserProfile
元件
既然我們已經定義了Popover
示例,是时候深入了解UserProfile
元件的詳細信息了。
這個元件出現在彈出視窗元件內。這個元件的目的是載入從JSON placeholder API取得的name
email
phone
website
詳細信息。
為了展示預取的例子,我們必須確保 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
屬性。這個屬性的目的是在组件挂載時載入附加數據。它展示了上面提到的解決方案#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>
);
}
這個應用程式的全部代碼可以在 這裡 找到。
過度的預取也可能導致執行變慢
一個建議,過度的預取並不是好事,因為:
-
它可能會拖慢您的應用程式。
-
如果預取沒有策略性地應用,它會降低用戶體驗。
當您了解用戶行為時才應該應用預取,也就是說,您能夠通過指標預測用戶行為,並且能夠判断他們是否經常访问某個頁面。在這種情況下,預取是一個好主意。
所以記住,总是要以策略性地應用預取。
總結
就是這些啦!希望您喜歡我的部落格文章。在這個部落格文章中,您學習到實現預取可以顯著提高您的网络應用程序的速度和響應性,從而提高用戶满意度。
想進一步閱讀,請參考以下文章:
Source:
https://www.freecodecamp.org/news/boost-web-performance-with-prefetching/