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
