5-6 搭配 useEffect 拉取多支 API 回傳的資料
本單元對應的專案分支為:fetch-forecast-data
。
#
單元核心這個單元的主要目標包含:
- 了解如何搭配 useEffect 拉取多支 API 回傳的資料
- 在 setSomething 中代入函式,以取得原本的資料狀態
到目前為止「臺灣好天氣」已經可以在載入時自動拉取資料,也可以在使用者點選「重新整理」時重新拉取資料,但是所需的資料還不完整,其中還沒有取得「天氣描述」、「降雨機率」,因此也無法更新天氣圖示。
在這個單元中,我們會使用中央氣象局提供另一支「天氣預報 API」來取得不足的資訊,並且學習在 React 元件中,如何一次發送多支 API 請求。
#
了解 API 回傳的天氣預報資料為了要取得「降雨機率」與「天氣描述」的資料,這裡會使用到前面曾說明過「一般天氣預報-今明 36 小時天氣預報」這支 API。同樣可以在線上說明文件試打「/v1/rest/datastore/F-C0032-001 一般天氣預報-今明 36 小時天氣預報」這支 API 來取得回應:
點擊「Try it out」,填入授權碼後,看看這支 API 會回應的資料內容。
從回應的內容中可以看到,我們一樣可以從 records.location
中取得和天氣有關的資料:
一樣在 location
屬性中的 weatherElement
中,可以看到提供了很多不同類型的資料:
從這些資料中可以取得最近 36 小時的天氣預報,並且將資料切成每 12 小時一份,因此在時間(time
)欄位中,一共會有三個資料。
對照著「預報 XML 產品預報因子欄位中文說明表(https://opendata.cwb.gov.tw/opendatadoc/MFC/D0047.pdf)」這份文件,可以知道回傳的資料裡面包含「天氣現象(Wx)」、「降雨機率(PoP)」、「舒適度(CI)」、「最高溫度(MaxT)」和「最低溫度(MinT)」:
也就是說,透過天氣預報這支 API 我們不只拿到了「降雨機率」,同時也可以透過「天氣現象」和「舒適度」來組成畫面中所需的「天氣描述」。另外在「天氣現象」回傳的資料中,還提供了天氣描述代碼(weatherCode
),後續將可以透過這個代碼來顯示對應的「天氣圖示」:
#
透過 fetch 取得天氣預報資料現在我們就可以透過剛剛找到的這支 API 來填補當初資料不足的部分。
#
修改資料狀態的名稱原本在定義資料狀態 state
的時候,是用 currentWeather
和 setCurrentWeather
:
但現在這個資料中不只包含當前的天氣資料,還包含從天氣預報中取得的雨量和天氣描述的資料,為了避免自己寫到後來混淆,先把資料的命名改成 weatherElement
:
原本程式中就有使用到 currentWeather
和 setCurrentWeather
的部分,記得也要一併改成 weatherElement
和 setWeatherElement
,如下圖所示:
#
撰寫 fetch 程式碼現在回到專案中一樣可以透過 fetch
請求天氣預報的資料,寫法會像這樣:
留意
這裡我們額外定義了一個變數名稱為 LOCATION_NAME_FORECAST
,值是「臺北市」,還記得前面曾經提過「天氣觀測」和「天氣預報」需要填入的 locationName
不同,「天氣觀測」要帶入的是「局屬觀測站」,而「天氣預報」要帶入是「縣市名稱」。這裡因為是呼叫「天氣預報」的 API,因此需要帶入的是「臺北市」而不是「臺北」,否則會無法正確取得資料。
#
撰寫呼叫天氣預報 API 的函式如同 fetchCurrentWeather
一樣,現在來撰寫一個 fetchWeatherForecast
的方法,把資料取回來後,過濾出我們需要的資料。
fetchWeatherForecast
的程式碼,邏輯基本上和 fetchCurrentWeather
是一樣的:
- 透過
reduce
過濾出所需要的天氣因子,包含「天氣現象(Wx)」、「降雨機率(PoP)」和「舒適度(CI)」。 - 這裡之所以使用了
item.time[0]
是因為在「未來 36 小時天氣預報」的資料中,會回傳三個時段的資料(每 12 小時一組),而我們要顯示的是即時天氣資訊,所以我們就只取最接近的 12 小時預報資料,也就是time
陣列中的第一個元素:
- 把資料透過
setWeatherElement
更新 React 元件的資料狀態中,但這麽做會有一些問題,將於後面說明
- 由於在元件中多了舒適度(
comfortability
)和天氣描述代碼(weatherCode
)的資料,因此記得在useState()
的預設值中,也把這兩個屬性的預設值放進去:
- 現在我們把這個寫好的方法,放到
useEffect
中去呼叫,像是這樣:
#
錯誤處理:留意 useState 的使用但是當我們這樣寫之後,你會看到「臺灣好天氣」中顯示溫度變成了 NaN
,部分資料也無法正常顯示,表示資料出現了一些問題:
為什麼會發生這樣的錯誤呢?
這並不新鮮,其實我們已經碰到過了,還記得之前我們有提到 setSomething
這個方法是會把舊有的資料全部清掉,用新的去覆蓋了,而這就是問題的原因。
因為我們呼叫了兩次不同的 API ,而且在裡面都各自使用了 setWeatherElement
,但我們只把透過 API 取得的資料放進去,而沒有把舊有的資料保留下來。時好時壞是因為這兩道 API 回傳資料的速度每次並不一定,而最後取得資料的會把一開始 weatherElement
中的資料覆蓋掉。有時候 fetchCurrentWeather
比較快得到結果,有時候則是 fetchWeatherForecast
比較快,所以才會有不一致的情況。
要解決這個問題只需要把原本 state 的狀態再重新放入 setSomething
的方法中即可,還記得在 setSomething
這個方法中可以透過帶入函式來取得原有的資料狀態(prevState
)嗎?這裡一樣可以透過這樣的方式,把在 weatherElement
中原有的狀態還去就可以了,寫法會像這樣:
#
修改原本呼叫 setWeatherElement 的地方在 fetchCurrentWeather
和 fetchWeatherForecast
的這兩個函式中,都有使用到了 setWeatherElement
的方法,因此都需要記得把原本的狀態給帶進去:
- 在
setWeatherElement
中帶入函式,並在函式的參數中帶入prevState
將可以取得原有的資料狀態 - 透過物件的解構賦值把原有的資料放進去,後面再放入透過 API 取得的資料
- 當箭頭函式單純只是要回傳物件時,可以連
return
都不寫,但回傳的物件需要使用小括號()
包起來 - 原本在
fetchCurrentWeather
的函式中,因為當時還沒辦法實際取得天氣描述和降雨機率,所以我們有先寫了假資料在它的setWeatherElement
中,這裡要記得一併移除這兩個屬性
這時候畫面就能正確呈現了。
#
修改當使用者點擊重新整理時呼叫的方法現在在使用者初次載入頁面時,會同時呼叫到 fetchCurrentWeather
和 fetchWeatherForecast
這兩個方法,但在使用者點擊重新整理的時候還不會,因此在原本 <Refresh onClick={fetchCurrentWeather} />
的地方,也要讓它能夠呼叫 fetchWeatherForecast
,於是可以把程式碼改成:
#
修改 weatherElement 的預設值現在你會發現,當頁面載入時,數字都會閃一下,因為它會先呈現我們在 useState
中的預設值,接著再拉取到中央氣象局的資料後,才把最新的資料帶入畫面中。現在既然我們已經有載入中的狀態,同時又可以取得最新的天氣資料,就可以把原本撰寫在 useState
中的預設值做個修改:
#
顯示天氣描述與舒適度最後讓我們在 JSX 中把最新取得的 comfortability
的資料也呈現出來:
#
換你了!取得天氣描述和降雨機率的資料在這個單元中,我們透過「一般天氣預報-今明 36 小時天氣預報」取得了「降雨機率」、「天氣描述」、「舒適度」與「天氣描述代碼」的資料。雖然現在已經能夠將資料正確顯示在畫面上,但程式碼還有可以改進的地方,我們將會在後續的單元中再來進行程式碼的重構。
現在要請你透過 fetch 取得資料,並整合到 App 元件的資料狀態中。同樣可以參考如下步驟:
- 檢視「一般天氣預報-今明 36 小時天氣預報」API 中回傳的資料內容,找到「降雨機率」、「描述」與「舒適度」的欄位
- 將原本透過 useState 取得的資料狀態改名為
weatherElement
和setWeatherElement
- 撰寫
fetchWeatherForecast
方法,在取得資料後使用setWeatherElement
更新元件資料狀態 - 在
setWeatherElement
中使用函式以取得原本的資料狀態(prevState
),並將此狀態保留在該物件中 - 在
onClick
中呼叫同時呼叫fetchCurrentWeather
和fetchWeatherForecast
方法 - 修改
useState
中資料的預設值 - 在 JSX 中顯示
description
和comfortability
的描述
本單元相關之網頁連結、完整程式碼與程式碼變更部分(時鐘圖示)可於 fetch-forecast-data
分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/fetch-forecast-data: