譯者 | 劉濤
審校 | 重樓
我們都曾遭遇過(guò)這樣的煩惱:漫長(zhǎng)的加載界面之后,卻只等來(lái)了毫無(wú)反應(yīng)的網(wǎng)頁(yè)。隨處可見(jiàn)的加載圖標(biāo)不停旋轉(zhuǎn),但一切似乎都停滯不前。讓我為你描繪一個(gè)更生動(dòng)的畫(huà)面:
這種情況通常發(fā)生的原因是,網(wǎng)站試圖在你一落地頁(yè)面就預(yù)取所有必要的數(shù)據(jù)。可能是正在處理某個(gè)API請(qǐng)求,或者多個(gè)API在順序預(yù)取數(shù)據(jù),導(dǎo)致頁(yè)面加載延遲。
結(jié)果如何?用戶(hù)體驗(yàn)簡(jiǎn)直糟糕到極點(diǎn)。你可能會(huì)想:"這么大的公司怎么會(huì)不注重用戶(hù)體驗(yàn)?真是令人失望。"因此,用戶(hù)往往會(huì)選擇離開(kāi)網(wǎng)站,這不僅影響了網(wǎng)站的關(guān)鍵指標(biāo),還可能導(dǎo)致其收入受到損失。
但是,如果我們能提前預(yù)取這些重量級(jí)頁(yè)面的數(shù)據(jù),讓用戶(hù)一進(jìn)入頁(yè)面就能立即與之交互,會(huì)怎樣呢?
這就是預(yù)取(Prefetching)概念的由來(lái),也正是我們?cè)谶@篇博文中將深入探討的內(nèi)容。
目錄
- 預(yù)取技術(shù):解決方案
- 預(yù)取技術(shù)如何提升用戶(hù)體驗(yàn)
- 問(wèn)題剖析
- 解決方案一:在父組件中預(yù)取數(shù)據(jù)
- 解決方案二:頁(yè)面加載時(shí)預(yù)取數(shù)據(jù)
- React中如何實(shí)現(xiàn)預(yù)取
- 過(guò)度預(yù)取也可能導(dǎo)致性能下降
- 總結(jié)
預(yù)取技術(shù):解決方案
針對(duì)上述問(wèn)題,我們的目標(biāo)是在頁(yè)面加載至網(wǎng)站之前,就預(yù)取該頁(yè)面所需的數(shù)據(jù),如此一來(lái),用戶(hù)在頁(yè)面加載時(shí)便無(wú)需再次預(yù)取數(shù)據(jù)。這種技術(shù)被稱(chēng)作預(yù)取。從技術(shù)層面來(lái)講,其定義如下:
預(yù)取是一種提前預(yù)取所需數(shù)據(jù)的方法,使主要組件無(wú)需等待數(shù)據(jù)即可加載,從而提升用戶(hù)體驗(yàn)。
這可以改善用戶(hù)體驗(yàn),增強(qiáng)客戶(hù)對(duì)你網(wǎng)站的信任。
預(yù)取是一種簡(jiǎn)潔而優(yōu)雅的解決方案,相較于標(biāo)準(zhǔn)流程,它更加以用戶(hù)為中心。要實(shí)施預(yù)取,我們需要了解用戶(hù)在網(wǎng)站上的行為。例如,最常訪問(wèn)的頁(yè)面,或哪些組件在小交互(如懸停)時(shí)預(yù)取數(shù)據(jù)。
完成對(duì)這些場(chǎng)景的分析后,就可以對(duì)其合理應(yīng)用預(yù)取技術(shù)。然而,作為開(kāi)發(fā)人員,我們應(yīng)該謹(jǐn)慎使用這個(gè)概念。過(guò)度預(yù)取也可能降低網(wǎng)站速度,因?yàn)槟阍噲D為未來(lái)場(chǎng)景預(yù)取大量數(shù)據(jù),這可能會(huì)阻塞主頁(yè)面的數(shù)據(jù)預(yù)取。
預(yù)取技術(shù)如何提升用戶(hù)體驗(yàn)
讓我們來(lái)看幾個(gè)預(yù)取技術(shù)有益的場(chǎng)景:
- 為登陸頁(yè)面上最常訪問(wèn)的鏈接提前加載數(shù)據(jù)/頁(yè)面。例如,假設(shè)你有一個(gè)"聯(lián)系我們"鏈接,此鏈接為用戶(hù)最常查看的,且加載時(shí)包含大量數(shù)據(jù)。與其在"聯(lián)系我們"頁(yè)面加載時(shí)才預(yù)取數(shù)據(jù),不如在主頁(yè)就開(kāi)始預(yù)取,這樣用戶(hù)就無(wú)需在"聯(lián)系我們"頁(yè)面上等待。
- 預(yù)取表格數(shù)據(jù),用于后續(xù)頁(yè)面。
- 在父組件中預(yù)取數(shù)據(jù),并將其加載到子組件中。
- 預(yù)取數(shù)據(jù)在彈出窗口中顯示。
這些都是在應(yīng)用中實(shí)現(xiàn)預(yù)取的方法,它們有助于提升用戶(hù)體驗(yàn)。
在本文中,我們將討論最后一個(gè)場(chǎng)景:"預(yù)取數(shù)據(jù)在彈出窗口中顯示"。這是一個(gè)預(yù)取技術(shù)能夠帶來(lái)的明顯好處,為用戶(hù)提供更流暢體驗(yàn)的典型示例。
問(wèn)題剖析
讓我為你詳細(xì)闡述這個(gè)問(wèn)題。請(qǐng)想象以下場(chǎng)景:
- 你有一個(gè)用于展示特定信息的組件。
- 這個(gè)組件內(nèi)部有一個(gè)元素,當(dāng)鼠標(biāo)懸停在其上時(shí)會(huì)顯示一個(gè)彈出窗口或工具提示。
- 這個(gè)彈出窗口在加載時(shí)需要預(yù)取數(shù)據(jù)。
現(xiàn)在,設(shè)想用戶(hù)將鼠標(biāo)懸停在該元素上,隨后等待數(shù)據(jù)被預(yù)取并顯示在彈出窗口中。在這段等待的時(shí)間里,用戶(hù)會(huì)看到一個(gè)骨架加載器(Skeleton Loader)。
這個(gè)場(chǎng)景大致如下(此為動(dòng)圖,需要下載anigif.ocx控件觀看):
每當(dāng)用戶(hù)將鼠標(biāo)懸停在圖片上時(shí),他們必須等待很長(zhǎng)時(shí)間,這真的很令人沮喪:(此為動(dòng)圖,需要下載anigif.ocx控件觀看)
要解決此問(wèn)題,有兩種解決方案可供你參考,以幫助你著手并根據(jù)自身需求優(yōu)化解決方案。
解決方案一:在父組件中預(yù)取數(shù)據(jù)
這個(gè)解決方案允許你在彈出窗口出現(xiàn)之前便已預(yù)取數(shù)據(jù),而非在組件加載時(shí)預(yù)取。
當(dāng)鼠標(biāo)懸停在某個(gè)元素(如圖片)上時(shí),彈出窗口會(huì)顯現(xiàn)。我們能夠在這個(gè)元素的父組件上實(shí)現(xiàn)鼠標(biāo)進(jìn)入時(shí)預(yù)取數(shù)據(jù)。鑒于此,在實(shí)際需要懸停顯示的組件(如圖片)之前,我們就已經(jīng)準(zhǔn)備好彈出窗口所需的數(shù)據(jù),并將這些數(shù)據(jù)傳遞給彈出窗口組件。
這種解決方案并不能完全消除加載狀態(tài),不過(guò)它能夠顯著降低用戶(hù)看到加載狀態(tài)的概率。(此為動(dòng)圖,需要下載anigif.ocx控件觀看)
解決方案二:頁(yè)面加載時(shí)預(yù)取數(shù)據(jù)
這個(gè)解決方案受到了類(lèi)似x.com(可能是指Facebook的前身或類(lèi)似的大型網(wǎng)站)的數(shù)據(jù)加載策略的啟發(fā),即在彈出窗口組件中,它們?cè)谥黜?yè)面加載時(shí)部分預(yù)取數(shù)據(jù),并在組件掛載時(shí)預(yù)取剩余的數(shù)據(jù)。(此為動(dòng)圖,需要下載anigif.ocx控件觀看)
正如你從上面的動(dòng)態(tài)圖中所看到的,用戶(hù)的個(gè)人資料詳細(xì)信息在彈出窗口中查看。仔細(xì)觀察,你會(huì)發(fā)現(xiàn)與關(guān)注者相關(guān)的詳細(xì)信息是稍后預(yù)取的。
當(dāng)你需要在彈出窗口中顯示大量數(shù)據(jù),但一次性預(yù)取所有數(shù)據(jù)可能對(duì)彈出窗口掛載或主頁(yè)面加載造成較大負(fù)擔(dān)時(shí),采取這種技術(shù)就顯得非常高效。
一個(gè)更好的解決方案是,在主頁(yè)面上部分加載所需的數(shù)據(jù),并在組件掛載時(shí)加載剩余的數(shù)據(jù)。
在這個(gè)例子中,我們?cè)谑髽?biāo)進(jìn)入圖片的父元素時(shí)預(yù)取了彈出窗口的數(shù)據(jù)。現(xiàn)在想象一下,一旦彈出窗口的數(shù)據(jù)加載完成,你還需要預(yù)取額外的詳細(xì)信息。因此,基于上述x.com的方法,我們可以在彈出窗口加載時(shí)預(yù)取額外的數(shù)據(jù)。這樣做的結(jié)果是:
在這里,我們采取以下步驟:
當(dāng)鼠標(biāo)進(jìn)入圖片的父組件時(shí),我們預(yù)取渲染彈出窗口所必需的主要數(shù)據(jù)。
這給我們足夠的時(shí)間來(lái)預(yù)取主要數(shù)據(jù)。
在彈出窗口加載時(shí),我們預(yù)取另一組數(shù)據(jù),即相冊(cè)數(shù)量。當(dāng)用戶(hù)閱讀姓名和郵箱等信息時(shí),下一組數(shù)據(jù)已經(jīng)準(zhǔn)備就緒,隨時(shí)可以展示。
通過(guò)這種方式,我們可以做一些小而巧妙的優(yōu)化,最大限度地減少用戶(hù)盯著屏幕上的加載動(dòng)畫(huà)發(fā)呆的時(shí)間。
React中如何實(shí)現(xiàn)預(yù)取
在本部分中,我們將簡(jiǎn)要介紹如何實(shí)現(xiàn)上述預(yù)取示例應(yīng)用程序。
項(xiàng)目設(shè)置
要開(kāi)始創(chuàng)建支持預(yù)取功能的應(yīng)用,請(qǐng)按照以下步驟操作:
你可以使用 Vite.js(這是我使用的工具)或 Create React App 來(lái)創(chuàng)建你的應(yīng)用程序。請(qǐng)在終端中使用以下命令:
yarn create vite prefetch-example --template react-ts
當(dāng)你用VS Code打開(kāi)prefetch-example文件夾后,應(yīng)該會(huì)看到如下的文件夾結(jié)構(gòu)。
現(xiàn)在讓我們深入了解一下我們將為這個(gè)應(yīng)用構(gòu)建的組件。
現(xiàn)在,讓我們深入了解為這個(gè)應(yīng)用程序所構(gòu)建的組件。
組件
在此示例中,我們將使用3個(gè)組件:
- PopoverExample
- UserProfile
- UserProfileWithFetching
PopoverExample 組件
讓我們從第一個(gè)組件開(kāi)始,即PopoverExample。這個(gè)組件在界面上展示了一個(gè)圖像頭像(avatar),并在其右側(cè)顯示了一些文本。它的布局應(yīng)該類(lèi)似于這樣:(此為動(dòng)圖,需要下載anigif.ocx控件觀看)
該組件的目的是作為一個(gè)示例,模擬現(xiàn)實(shí)生活中的場(chǎng)景。在這個(gè)組件中,當(dāng)用戶(hù)將鼠標(biāo)懸停在圖片上時(shí),會(huì)加載一個(gè)彈出窗口組件。
以下是該組件的代碼:
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>
);
}
程序運(yùn)行的過(guò)程,讓我逐步解釋?zhuān)?/span>
- 我們有一個(gè)名為“hover-example”的父文件div,其中包含一張圖像和一些文本。
- 接下來(lái),我們有條件地渲染了一個(gè)具有“floating”類(lèi)名的 div文件。這便是實(shí)際的彈出組件,當(dāng)你將鼠標(biāo)懸停在圖像上時(shí),它就會(huì)開(kāi)啟。我們使用了floating-ui庫(kù)及其基本的懸停示例來(lái)實(shí)現(xiàn)彈出窗口的懸停效果。
- 在彈出窗口中,我們有條件地加載UserProfile組件和骨架加載器。當(dāng)我們正在預(yù)取用戶(hù)資料的數(shù)據(jù)時(shí),這個(gè)骨架加載器便會(huì)顯現(xiàn)。稍后我會(huì)更為詳盡地闡釋這一點(diǎn)。
- 在 MyLoader 組件中,我們采用了react-content-loader庫(kù)。該庫(kù)還設(shè)有一個(gè)網(wǎng)站,能夠幫助你創(chuàng)建加載器。
UserProfile 組件
既然我們已經(jīng)定義了 Popover 示例,那么此刻便是深入探究 UserProfile 組件細(xì)節(jié)的時(shí)候了。
這個(gè)組件出現(xiàn)在Popover組件內(nèi)部。其目的是加載從JSON占位符API預(yù)取的name、email、phone和website詳細(xì)信息。
為了演示預(yù)取示例,我們必須確保UserProfile組件僅作為展示組件存在;也就是說(shuō),它內(nèi)部不包含任何明確的預(yù)取邏輯。
關(guān)于這個(gè)組件的關(guān)鍵點(diǎn)是,數(shù)據(jù)的預(yù)取發(fā)生在父組件PopoverExample中。在這個(gè)組件里,當(dāng)鼠標(biāo)進(jìn)入該組件(即觸發(fā)mouseenter事件)時(shí),我們開(kāi)始預(yù)取數(shù)據(jù)。這是我們之前討論過(guò)的解決方案#1。
這為用戶(hù)在將鼠標(biāo)懸停在圖像上之前提供了充足的時(shí)間來(lái)預(yù)取數(shù)據(jù)。以下是相關(guān)代碼:
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>
);
}
此應(yīng)用程序的完整代碼可在此處找到。
過(guò)度預(yù)取也可能導(dǎo)致性能下降
一點(diǎn)建議:預(yù)取過(guò)多并不是一個(gè)好主意,原因如下:
- 可能會(huì)降低應(yīng)用速度。
- 如果預(yù)取策略不當(dāng),會(huì)損害用戶(hù)體驗(yàn)。
預(yù)取需要基于用戶(hù)行為預(yù)測(cè):只有當(dāng)你能通過(guò)數(shù)據(jù)分析預(yù)測(cè)用戶(hù)的下一步操作時(shí),預(yù)取才是有意義的。比如,如果你能通過(guò)用戶(hù)的歷史訪問(wèn)記錄預(yù)測(cè)他們經(jīng)常訪問(wèn)的頁(yè)面,那么在這些頁(yè)面上進(jìn)行預(yù)取就是一個(gè)好主意。
因此,請(qǐng)記住要始終策略性地應(yīng)用預(yù)取技術(shù)。
總結(jié)
在這篇文章中,你了解到實(shí)現(xiàn)預(yù)取可以顯著提升你的Web應(yīng)用程序的速度和響應(yīng)性,從而提高用戶(hù)滿意度。
為了進(jìn)一步閱讀,請(qǐng)參考以下文章:
原文標(biāo)題:How to Boost Web Performance with Prefetching – Improve User Experience by Reducing Load Time,作者:Keyur Paralkar