React 的Suspense 功能令人興奮,它即將到來,將使開發人員能夠輕鬆地讓其組件延遲渲染,直到它們“準備好”,從而帶來更流暢的用戶體驗。 “準備好”在此處可以指許多方面。例如,您的數據加載實用程序可以與Suspense 結合使用,允許在任何數據正在傳輸時顯示一致的加載狀態,而無需為每個查詢手動跟踪加載狀態。然後,當您的數據可用且您的組件“準備好”時,它將進行渲染。這是最常與Suspense 一起討論的主題,我之前也寫過關於它的文章;但是,數據加載只是Suspense 可以改善用戶體驗的眾多用例之一。我今天想談論的另一個用例是圖像預加載。
您是否曾經製作或使用過一個Web 應用,在該應用中,在到達屏幕後,您的位置會隨著圖像下載和渲染而發生抖動和跳躍?我們稱之為內容重排,它既令人震驚又令人不快。 Suspense 可以幫助解決這個問題。您知道我說過Suspense 的全部意義在於阻止組件渲染,直到它準備好為止嗎?幸運的是,“準備好”在此處非常開放——出於我們的目的,可以包括“我們需要的已預加載的圖像”。讓我們看看如何操作!
Suspense 快速入門
在深入探討細節之前,讓我們快速了解Suspense 的工作原理。它有兩個主要部分。首先是組件掛起的概念。這意味著React 嘗試渲染我們的組件,但它尚未“準備好”。發生這種情況時,組件樹中最接近的“後備”將進行渲染。我們很快就會看看如何製作後備(這相當簡單),但是組件告訴React 它尚未準備好的方式是拋出一個Promise。 React 將捕獲該Promise,意識到組件尚未準備好,並渲染後備。當Promise 解析時,React 將再次嘗試渲染。重複此過程。是的,我有點過於簡化了,但這正是Suspense 工作原理的要點,我們將在進行過程中擴展其中一些概念。
Suspense 的第二個部分是引入“過渡”狀態更新。這意味著我們設置狀態,但告訴React 狀態更改可能會導致組件掛起,如果發生這種情況,則不渲染後備。相反,我們希望繼續查看當前屏幕,直到狀態更新準備好,此時它將進行渲染。當然,React 為我們提供了一個“pending”布爾指示器,讓開發人員知道此過程正在進行中,以便我們可以提供內聯加載反饋。
讓我們預加載一些圖像!
首先,我想指出,本文末尾有一個我們正在製作的完整演示。如果您只想跳入代碼,請隨時現在打開演示。它將展示如何將Suspense 與過渡狀態更新結合使用來預加載圖像。這篇文章的其餘部分將逐步構建該代碼,並沿途解釋如何以及為什麼。
好的,讓我們開始吧!
我們希望我們的組件掛起,直到所有圖像都已預加載。為了盡可能簡化操作,讓我們創建一個<suspenseimage></suspenseimage>
組件,該組件接收src 屬性,預加載圖像,處理異常拋出,然後在一切準備就緒時渲染一個<img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174358885649082.png?x-oss-process=image/resize,p_40" class="lazy" alt="Pre-Caching Images with React Suspense">
在HTML 中使用圖像,但我們也可以使用JavaScript 中的Image()
對像以命令式方式創建圖像;此外,我們這樣創建的圖像具有一個onload
回調,該回調在圖像…加載時觸發。它看起來像這樣:
const img = new Image(); img.onload = () => { // 圖像已加載};
但是我們如何將其與異常拋出結合起來呢?如果您像我一樣,您首先可能會想到類似這樣的東西:
const SuspenseImg = ({ src, ...rest }) => { throw new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve(); }; img.src = src; }); return<img alt="" src="%7Bsrc%7D"> ; };
當然,問題在於這將始終拋出一個Promise。每次React 嘗試渲染<suspenseimg></suspenseimg>
實例時,都會創建一個新的Promise,並立即將其拋出。相反,我們只想在圖像加載之前拋出一個Promise。有一句老話,計算機科學中的每個問題都可以通過添加一層間接性來解決(除了間接性層數過多的問題),所以讓我們這樣做,並構建一個圖像緩存。當我們讀取src 時,緩存將檢查它是否已加載該圖像,如果沒有,它將開始預加載並拋出異常。而且,如果圖像已預加載,它將只返回true 並讓React 繼續渲染我們的圖像。
這是我們的<suspenseimage></suspenseimage>
組件的樣子:
export const SuspenseImg = ({ src, ...rest }) => { imgCache.read(src); return<img src="%7Bsrc%7D" alt="用反應懸念的預處理圖像" > ; };
這是我們緩存的最小版本的樣子:
const imgCache = { __cache: {}, read(src) { if (!this.__cache[src]) { this.__cache[src] = new Promise((resolve) => { const img = new Image(); img.onload = () => { this.__cache[src] = true; resolve(this.__cache[src]); }; img.src = src; }); } if (this.__cache[src] instanceof Promise) { throw this.__cache[src]; } return this.__cache[src]; } };
它並不完美,但目前足夠了。讓我們繼續使用它。
實現
請記住,下面有一個指向完整工作演示的鏈接,因此,如果我在任何特定步驟中移動得太快,請不要絕望。我們將邊走邊解釋。
讓我們從定義我們的後備開始。我們通過在組件樹中放置一個Suspense 標記來定義後備,並通過fallback 屬性傳遞我們的後備。任何掛起的組件都將向上搜索最近的Suspense 標記,並渲染其後備(但是如果沒有找到Suspense 標記,則會拋出錯誤)。一個真正的應用程序可能在整個過程中都有許多Suspense 標記,為其各個模塊定義特定的後備,但對於此演示,我們只需要一個包裝我們的根應用程序的標記。
function App() { return ( <suspense fallback="{<Loading"></suspense> }> <showimages></showimages> ); }
<loading></loading>
組件是一個基本的旋轉器,但在實際應用程序中,您可能希望渲染實際嘗試渲染的組件的某種空殼,以提供更無縫的體驗。
有了這個,我們的<showimages></showimages>
組件最終將使用以下內容渲染我們的圖像:
<div> {images.map((img) => ( <div key="{img}"> <suspenseimg alt="" src="%7Bimg%7D"></suspenseimg> </div> ))} </div>
在初始加載時,我們的加載旋轉器將顯示,直到我們的初始圖像準備好,此時它們將同時顯示,沒有任何交錯的重排卡頓。
過渡狀態更新
一旦圖像到位,當我們加載下一批圖像時,我們希望它們在加載後顯示,當然,在它們加載時保持屏幕上的現有圖像。我們使用useTransition
鉤子來做到這一點。這將返回一個startTransition
函數和一個isPending
布爾值,它指示我們的狀態更新正在進行中,但已掛起(或者即使它尚未掛起,如果狀態更新只是花費的時間太長,也可能仍然為true)。最後,當調用useTransition
時,您需要傳遞一個timeoutMs
值,這是isPending
標誌可以為true 的最大時間量,之後React 將放棄並渲染後備(請注意, timeoutMs
參數可能會在不久的將來被刪除,當更新現有內容時,過渡狀態更新只需等待盡可能長的時間)。
這是我的樣子:
const [startTransition, isPending] = useTransition({ timeoutMs: 10000 });
在我們的後備顯示之前,我們將允許10 秒鐘過去,這在現實生活中可能太長了,但對於此演示來說是合適的,尤其是在您可能故意在DevTools 中降低網絡速度以進行實驗時。
以下是如何使用它。當您單擊加載更多圖像的按鈕時,代碼如下所示:
startTransition(() => { setPage((p) => p 1); });
該狀態更新將使用我的GraphQL 客戶端micro-graphql-react 觸發新的數據加載,該客戶端與Suspense 兼容,在查詢正在進行時將為我們拋出一個Promise。一旦數據返回,我們的組件將嘗試渲染,並在我們的圖像預加載時再次掛起。在所有這些事情發生的同時,我們的isPending
值將為true,這將允許我們在現有內容的頂部顯示加載旋轉器。
避免網絡瀑布
您可能想知道React 在圖像預加載正在進行時如何阻止渲染。使用上面的代碼,當我們這樣做時:
{images.map((img) => (
……以及其中渲染的<suspenseimage></suspenseimage>
,React 是否會嘗試渲染第一張圖像,掛起,然後重新嘗試列表,超過第一張圖像(現在在我們的緩存中),然後掛起第二張圖像,然後是第三張、第四張等等。如果您之前閱讀過關於Suspense 的內容,您可能想知道我們是否需要在所有這些渲染髮生之前手動預加載列表中的所有圖像。
事實證明,無需擔心,也無需進行尷尬的預加載,因為React 對其在Suspense 世界中渲染事物的方式相當聰明。當React 遍歷我們的組件樹時,它不會在遇到掛起時停止。相反,它會繼續嘗試渲染我們組件樹中的所有其他路徑。因此,是的,當它嘗試渲染圖像0 時,將發生掛起,但React 將繼續嘗試渲染圖像1 到N,然後才掛起。
您可以通過在加載新圖像集時查看完整演示中的“網絡”選項卡來查看此操作。您應該會看到整個圖像桶立即顯示在網絡列表中,一個接一個地解析,並且完成後,結果應該顯示在屏幕上。為了真正放大這種效果,您可能希望將網絡速度降低到“快速3G”。
為了好玩,我們可以通過在React 嘗試渲染我們的組件之前手動從我們的緩存中讀取每個圖像來強制Suspense 遍歷我們的圖像,遍歷組件樹中的每條路徑。
images.forEach((img) => imgCache.read(img));
我創建了一個演示來說明這一點。如果您同樣在加載新圖像集時查看“網絡”選項卡,您將看到它們按順序添加到網絡列表中(但不要在降低網絡速度的情況下運行此操作)。
延遲掛起
在使用Suspense 時,需要記住一個推論:盡可能在渲染的後期和組件樹的低層掛起。如果您有一些渲染一堆掛起圖像的<imagelist></imagelist>
,請確保每個圖像都在其自己的組件中掛起,以便React 可以單獨訪問它,這樣就不會有任何圖像阻塞其他圖像,從而導致瀑布。
此規則的數據加載版本是,數據應盡可能由實際需要它的組件加載。這意味著我們應該避免在一個組件中執行以下操作:
const { data1 } = useSuspenseQuery(QUERY1, vars1); const { data2 } = useSuspenseQuery(QUERY2, vars2);
我們想要避免這種情況的原因是,查詢一將掛起,然後是查詢二,導致瀑布。如果這根本無法避免,我們將需要在掛起之前手動預加載這兩個查詢。
演示
這是我承諾的演示。它與我上面鏈接的演示相同。
打開演示如果您運行它並打開開發工具,請確保取消選中DevTools 網絡選項卡中顯示的“禁用緩存”框,否則您將破壞整個演示。
該代碼幾乎與我之前顯示的代碼相同。演示中的一個改進是我們的緩存讀取方法具有以下行:
setTimeout(() => resolve({}), 7000);
很好地預加載所有圖像,但在現實生活中,我們可能不想無限期地阻止渲染,僅僅是因為一兩張落後的圖像加載緩慢。因此,在一段時間後,我們只需發出綠燈,即使圖像尚未準備好。用戶將看到一兩張圖像閃爍,但這比忍受軟件凍結的挫敗感要好。我還想指出,七秒鐘可能過長了,但對於此演示,我假設用戶可能會在DevTools 中降低網絡速度以更清晰地查看Suspense 功能,並希望支持這一點。
該演示還有一個預緩存圖像複選框。默認情況下選中它,但您可以取消選中它以使用普通的<img alt="用反應懸念的預處理圖像" >
標記替換<suspenseimage></suspenseimage>
組件,如果您想將Suspense 版本與“普通React”進行比較(只是不要在結果出現時選中它,否則整個UI 可能會掛起並渲染後備)。
最後,與CodeSandbox 一樣,某些狀態有時可能會不同步,因此如果事情開始看起來奇怪或損壞,請點擊刷新按鈕。
雜項
在將此演示放在一起時,我意外地犯了一個巨大的錯誤。我不希望演示的多次運行會因為瀏覽器緩存它已經下載的圖像而失去其效果。因此,我使用緩存破壞器手動修改所有URL:
const [cacheBuster, setCacheBuster] = useState(INITIAL_TIME); const { data } = useSuspenseQuery(GET_IMAGES_QUERY, { page }); const images = data.allBooks.Books.map( (b) => b.smallImage `?cachebust=${cacheBuster}` );
INITIAL_TIME 在模塊級別(即全局)使用以下行定義:
const INITIAL_TIME = new Date();
如果您想知道為什麼我沒有這樣做:
const [cacheBuster, setCacheBuster] = useState( new Date());
……這是因為這會造成可怕的可怕後果。在第一次渲染時,圖像嘗試渲染。緩存導致掛起,React 取消渲染並顯示我們的後備。當所有Promise 都已解析後,React 將嘗試重新進行初始渲染,並且我們的初始useState 調用將重新運行,這意味著:
const [cacheBuster, setCacheBuster] = useState( new Date());
……將重新運行,並具有新的初始值,導致一組全新的圖像URL,這將再次無限期地掛起。組件將永遠不會運行,並且CodeSandbox 演示會停止運行(這使得調試變得令人沮喪)。
這似乎是一個由此特定演示的獨特要求引起的奇怪的特殊問題,但有一個更大的教訓:渲染應該是純淨的,沒有副作用。 React 應該能夠多次嘗試重新渲染您的組件,並且(給定相同的初始道具)應該從另一端獲得相同的確切狀態。
以上是用反應懸念的預處理圖像的詳細內容。更多資訊請關注PHP中文網其他相關文章!

前幾天我得到了這個問題。我的第一個想法是:奇怪的問題!特異性是關於選擇者的,而在符號不是選擇器,那麼...無關緊要?

在這篇文章中,我們將使用我構建和部署的電子商務商店演示來進行Netlify,以展示如何為傳入數據製作動態路線。這是一個公平的


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

記事本++7.3.1
好用且免費的程式碼編輯器