5-5 實作資料載入中的狀態
本單元對應的專案分支為:show-loading-status
。
#
單元核心這個單元的主要目標包含:
- 了解為什麼需要製作「資料載入中」的狀態
- 了解實作「資料載入中」的方法
- 了解如何從
setState()
中透過帶入函式的方式取得未變更前的資料狀態
現在當頁面一載入,或當使用者點擊了重新整理的按鈕時,就會透過中央氣象局的 API 重新拉取最新的觀測資料,並更新畫面內容,但如果使用者再點一次,因為畫面上沒有任何資料有變動,使用者有些時候會不太確定到底是自己沒點到,或是點到了但資料正在載入中。
#
什麼是資料載入中的狀態為了著重良好的使用者體驗,不要讓使用者不清楚發生了什麼事,許多網站都會實作「資料載入中」的畫面。特別是由於現今網站許多都是透過 AJAX 去向後端伺服器拉取資料回來呈現,拉取資料的過程中必然需要消耗一些時間,因此處理「載入中」的狀態算是現在每個網站都需要考慮到的部分。
以 Instagram 為例,一開始進入網頁的時候,會先看到一個「空畫面」:
接著會出現一個「空殼」,可以注意到右下角有一個載入中的圖示,也就是那一朵像花會轉動的東西,相信大家一定不陌生:
那麼回到我們的即時天氣 App 中可以怎麼樣做呢?
#
實作載入中的資料狀態 - isLoading#
定義「載入中」的資料狀態 - isLoading現在可以在 useState
的地方,多添加一個 isLoading
的狀態,當它是 true
時表示資料還在載入中,這時候就可以顯示對應載入的畫面。一般會把 isLoading
的預設值設為 true
,表示一進來的時候就正在拉取資料:
提醒
這裡也可以把 isLoading
拆成另一個 state
,但這裡考量到載入完成指的就是天氣資料(currentWeather
)是否已經載入完成,因此多數時候是 isLoading
和 currentWeather
是會一起變動的,所以才把 isLoading
的狀態放在 currentWeather
物件中。
#
資料取得後修改 isLoading 的狀態接著在原本更新 currentWeather
函式中,透過 setCurrentWeather
把 isLoading
的值改為 false
,表示資料已經載入完畢:
#
點擊重新整理時,再次將 isLoading 改為 true - 使用 prevState現在,當使用者初次進來網站時,一開始的 isLoading
會是 true
,也就是資料正在載入中;待 fetchCurrentWeather
的資料都回來之後,isLoading
會變成 false
。
但還有一個情況是當使用者點擊右下角的重新整理時,也需要把 isLoading
的狀態改成 true
,這時候我們可以在 fetchCurrentWeather
實際開始向 API 拉取資料前,先把 isLoading
的狀態設成 true
。
但要注意的是,像下圖這種寫法是會產生錯誤的:
我們需要在呼叫 fetch 之前去將 isLoading
改成 true
這部分的邏輯是沒有錯的,但還記得在前面的單元中,筆者曾提到「每次 setSomething 時都是用新的資料覆蓋舊的」,所以這裡如果直接用:
那麼整個 currentWeather
的資料狀態都會被覆蓋掉,變成只剩下 { isLoading: true }
。
好在,透過 useState
產生的 setSomething
這個方法中,參數不只可以帶入物件,還可以帶入函式,透過這個函式就可以取得「更新前的資料狀態」,慣例上我們會把前一次的資料狀態取名為 prevState
,接著透過物件的解構賦值把原本的資料狀態(prevState
)放入物件中,再把要更新的資料狀態放進去:
因此,這裡的 setCurrentWeather
中,只需要帶入函式就可以取得原本的資料狀態,再透過物件的解構賦值把原有資料帶進去,更新 isLoading
的狀態改成 true
就可以了,程式碼會修改成下面這樣:
到這一步後,一開始畫面載入或使用者點選「更新按鈕」時 isLoading
會是 true
,資料載入完畢後 isLoading
會變成 false
。如果你不確定是不是有正確修改的話,可以在 return
JSX 的地方,使用 console.log(currentWeather.isLoading)
看一下:
從瀏覽器的開發者工具中可以看到:
- 一開始網頁載入時,畫面一共會轉譯三次,
isLoading
的狀態分別是true
(預設值)->true
(拉資料前)->false
(拉完資料後) - 當使用者點選更新按鈕後,畫面會轉譯兩次,
isLoading
的狀態分別是true
(拉資料前) ->false
(拉完資料後)
#
撰寫載入中要顯示的樣式現在透過 isLoading
的狀態,我們已經可以清楚知道什麼時候是正在拉取資料,什麼時候已經取得資料,因此就可以來撰寫載入中要顯示的樣式了。
這裡載入中的提示很簡單,當資料在載入中的時候,右下角的「更新按鈕」就改成顯示「載入中」的圖示,並搭配旋轉的動畫效果。
#
根據載入狀態切換顯示圖示現在就可以根據 isLoading
的狀態來切換要顯示的是「更新圖示」或「載入中圖示」,切換圖示的方式就和前一個單元使用到的條件轉譯一樣:
- 從
./src/images
資料夾中載入 loading 圖示,並取名為LoadingIcon
- 使用三元判斷式來做到條件轉譯,當
isLoading
為true
時顯示LoadingIcon
否則顯示RefreshIcon
,也就是:
現在,只要資料在載入中,右下角的圖示就會變成 LoadingIcon,載入完畢後就會變回原本的 RefreshIcon。如果你的網路速度很快的話,可能會發現你幾乎看不到 Loading 圖示,這時候你可以如下圖所示,在 Network
面板把自己的網速暫時調慢(Slow 3G)後,再重新整理試試看:
提示,除了把網速調慢來檢視
LoadingIcon
的效果,讀者也可以試著使用 React DevTools 來把isLoading
的狀態改成true
試試看!
#
增加圖示旋轉的效果只是這種效果設計師是不會滿意的,因為一點都沒有「載入中」的感覺,這裡我們得要加上一點旋轉的效果來讓它看起來更像是在「載入中」。
我們只需要透過 CSS 的 animation
就可以讓圖示旋轉,回到使用 styled components 撰寫 Refresh
CSS 樣式的地方,只需要在這裡面加上 animation
的效果就可以了:
1.使用 @keyframes
定義旋轉的動畫效果,並取名為 rotate
,接著在裡面使用 CSS 的 transform rotate 來產生旋轉的動畫
- 針對
svg
圖示透過animation
屬性套用rotate
動畫效果
現在你應該會看到不論資料是否在載入中,畫面上的 <Refresh />
圖示都會一直旋轉,因為我們並沒有告訴它說什麼時候要開始或停止套用旋轉的動畫。
#
只有在載入資料時才旋轉 - 把資料透過 props 傳入 Styled Component 內還記得曾經在前面的單元中提過,透過 CSS-in-JS 的這種寫法,除了可以在 CSS 中使用 JavaScript 外,還可以把資料透過 props 傳到 Styled Component 內,讓 CSS 可以根據這個資料來調整套用的樣式。
因此這裡可以這樣做:
- 是否正在載入中的
isLoading
資料狀態透過 props 帶入<Refresh>
這個 styled components 中
- 在
Refresh
中的animation-duration
屬性把傳入的 props 取出,直接透過物件的解構賦值取出isLoading
,並以此判斷是否要執行動畫,當animation-duration
為0s
的話表示則不旋轉:
現在不論是第一次載入頁面,或是當你按下重新整理的按鈕,只有當資料真正時處於載入中的狀態時(isLoading = true
),這個圖示才會套用旋轉的效果。
#
使用解構賦值讓版面更乾淨現在在 <App />
元件的 JSX 中,使用了非常多 currentWeather.observationTime
、currentWeather.locationName
、currentWeather.temperature
...,這樣寫起來非常繁瑣,因為一直要寫 currentWeather.ooo
。
因此在 React 中對於物件類型的資料,經常會使用物件的解構賦值方法,先把要使用到的資料取出來,像是這樣:
如此,在 JSX 中就可以直接使用這些變數,而不需要在前面多加上 currentWeather.ooo
,修改後的程式碼會變得更加精簡:
#
換你了!完成資料載入中的狀態現在換你來完成資料載入中的狀態。可以參考以下流程:
- 在
currentWeather
中添加isLoading
的資料,並預設為true
- 當從 API 取得資料後將
isLoading
的值改為false
- 若使用者點擊重新整理的按鈕,則要先讓
isLoading
變成 true,這裡要留意如何從setState()
中以帶入函式的方式取得未變更前的資料狀態 - 載入 Loading 圖示
- 透過 CSS 增加旋轉的動畫效果,讓 Loading 圖示可以旋轉
- 將 isLoading 的資料狀態以 props 傳入
<Refresh>
中,並根據此狀態決定要不要讓圖示套用旋轉動畫 - 使用物件的解構賦值,將所需的資料從
currentWeather
中取出,並帶入 JSX 內
本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 show-loading-status
分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/show-loading-status