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:
