選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
8 分鐘閱讀時間 (1591 個字)

React Suspense

front-end

前言

在 React Conf 2019 介紹了許多關於 Concurrent ModeSuspense for Data Fetching 的資訊

今天我們主要介紹一下 Suspense for Data Fetching

再往下看之前可以先看看官網的文章

今天介紹的主題主要以下面三個題目

  • Suspense 是什麼
  • Suspense for data fetching
  • Supspense 可以做到什麼

Suspense 是什麼

Suspense是一個可以讓還沒準備好可以 render 的 UI 可以顯示為 loading,主要要解決兩個問題

  1. Code Split (延後程式碼載入)
  2. Data Fetching

Code Split 的部分,由於 React 做出來的網頁是 SPA,在進入網頁是會將所有頁面需要的程式碼都下載下來,但當 web app 越做越大,需要下載的程式碼也越大,為了解決這個問題 React 提出了 Code Split (延後程式碼載入) 的方法來讓還不需要使用到的 component 可以等到需要再下載下來。

例如一個網站可能有 首頁個人資料頁面 等等,一開始進來可能只需要到首頁,就可以使用 lazy import 的方法,等到頁面切換到 個人資料頁面 時再將需要的 component 載入。

React v16.6 時已經解決了 Code Split 的問題,像是下面使用 React.lazy 載入 component,當 component 還沒載入完成時,會顯示 fallback 裡面的 Spinner。

不過今天主要要講的是 Data Fetching 的部分

Suspense for data fetching

這邊先來介紹一下 React 在撈資料的三種方法

  1. Fetch-On-Render
  2. Fetch-Then-Render
  3. Render-As-You-Fetch

Fetch-On-Render

使用 componentDidMount 或是 useEffect 去抓資料就是屬於這種,在這三種方法裡面這個方法是屬於效率/體驗最差的,Render 之後再去呼叫 API

如下:

假設今天每一個 Component 都有一個 useEffect 來撈資料,並且層層疊再一起時還會發生抓資料的 water fall

Render 的流程就會像下面這樣需要的上層 component 的 fetch 完成下面才能開始 fetch

fetch user => 等 =>完成 => fetch Post => 等 => 完成

Fetch-on-Render 效果如下:

這邊可以注意看一下 Console 顯示的時間,我是將 fetch user 跟 fetch post 的時間設為固定的(就是假的 api 拉,設定過幾秒再回傳)

fetch user 需要花費 1 秒,fetch post 需要花費 2 秒,而 console 顯示得時間是從頁面載入後完成這個 fetch 已經過了多久

以上面的圖為例,由於要等上層 component fetch 完才能 fetch 下層的 component 需要的資料,所以總時長就是 1+ 2 =3 秒

Fetch-Then-Render

上面我們看到 fetch-on-render 的缺點是必須等到 render 完成之後才能 fetch 資料,且上層 component 還沒 render 完成之前下層的都不能 fetch 資料,我們可以透過一些 Library (像是 GraphQL or Relay)可以一次拿到 Profile 和 Post 的資料再 render 出來,不過這邊我們不使用這些 Library 而是做出一個類似的範例

將 fetchUser 和 fetchPosts 用 promise.all 包起來達到一起呼叫的功能

而在 component 中的使用如下:

注意到我們是在 render 之前 fetch 資料,等到 render 之後才取資料

Render 的流程如下

fetchUser =>fetchPost => 等 => 完成 fetchUser =>完成 fetchPost

render 的效果如下:

可以看到這次總花費時間變成只有兩秒了(gif 有點快可能要看仔細一點)

雖然目前解決了需要等到上層 component fetch 完資料後,下層才能呼叫的問題,但也產生了另外一個問題

就是我們必須要等 fetchUser 和 fetchPost 都完成後才能顯示東西,因為我們使用 Promise.all

Render-As-You-Fetch

在上一個步驟我們先 fetch 資料才呼叫 setState

  1. Start Fetching
  2. Finish Fetching
  3. Render

在這邊我們使用 suspense,一樣,先 fetch 資料,不過這次不等資料回來,我們先 render

  1. Start Fetching
  2. Render
  3. Finish Fetching

使用 Suspense 的程式碼如下:

createResource 這個物件我們等等再解釋,現在先把他想像成一個神奇的物件,以 resource.user.read() 來解釋

呼叫 resource.user.read() 會檢查是否已經 fetch 到 user 的資料了,如果有資料,會 throw user 的資料,如果沒有,會 throw 一個 Promise

注意到我這邊用的是 throw ,這樣就很好解釋 Suspense 的原理了。

可以把 Suspense 想像成一個 component 的 try catch

Like this:

而 createSource 的實作方法如下:

簡單來說就是用 wrapPromise 包住 fetch,使用 read 時如果還在 fetch 會 throw promise,不過這部分 React 還在調整,目前沒有確定一定要這樣做,所以可以先不用參考,只要知道是用類似的方法達成的就可以了

再來 Suspense 是有 顯示 順序的,以下面的圖為例,顯示的順序為 外層的 ProfileDetails => 內層的 Posts

這樣的好處是我們可以控制什麼東西需要先顯示,例如這邊的例子,希望先顯示 user 名稱,才顯示他寫的文章

流程如下:

顯示的效果如下:

可以看到現在只要作者名稱撈到資料了就會先顯示,如果文章還沒撈到會顯示 loading,且如果是文章先撈到,也會等使用者名稱撈到在顯示

Suspense 可以做到什麼

  1. 提早 fetch 從剛剛的流程圖我們可以看到使用 suspense 不但可以同時撈資料,且不需等全部資料回傳才能顯示
  2. 設定顯示的順序下面的三張圖是 React Conf 上面的範例圖片,可以看到使用 Suspense 對於 UI 的體驗有非常大的提升

3. 解決 race condition

假設目前有兩個 Component,一個顯示作者名稱,一個顯示作者文章,當 id 改變時會重新 fetch 資料,這個時候假設 fetchUser 比 fetchPosts 慢,就會發生明明上面顯示的是 A 作者,卻顯示 B 作者寫的文章

程式碼如下:

效果如下:

這邊這個範例是有兩個 component,這兩個 component 都會根據 id 的變化來撈資料,例如 ProfilePage 這個 component 就是當 id 變化時撈使用者資料,並顯示使用者名稱,Post 這個 component 就是當 id 變化時撈使用者寫的文章

由於兩個 component 的撈資料是分開進行的,所以會造成明明是 A user 的名字卻顯示 B user 得文章,如同上方 gif 顯示的效果。

使用 Suspense 之後我們就可以確保兩個 fetch 是同時執行去抓資料,並且不論誰先抓到資料,也會照我們想要的順序顯示

顯示效果如下:

結語

有關 Supense 的介紹就到這邊,謝謝從頭看完的人!如果有說的不對的部分再麻煩指證

參考資料如下:

Izpack 基本應用
解析 OWASP API Security Top 10
 

評論 1

Guest - Lake 於 2021/05/31, 週一 09:42

有讀有推

有讀有推
已經注冊了? 這裡登入
Guest
2024/04/29, 週一

Captcha 圖像