5-3 使用 fetch 拉取天氣觀測資料
本單元對應的專案分支為:get-current-weather-when-refresh-clicked。
單元核心#
這個單元的主要目標包含:
- 進一步了解「局屬氣象站-天氣觀測資料」API 回傳的資料內容
- 串接「局屬氣象站-天氣觀測資料」API 回傳的資料,並顯示於畫面
- 留意 useState 中資料為物件時,更新狀態的使用
在上一個單元中,我們先根據中央氣象局 API 回傳的資料結果,當成 currentWeather 的預設值來呈現。在這個單元中,我們將透過瀏覽器提供的 fetch API 來實際發送請求,並更新畫面上呈現的資料。
由於「臺灣好天氣」中要呈現的資料,會需要分別透過「天氣觀測」與「天氣預報」這兩支 API 取得,在這個單元先來處理「天氣觀測」的資料部分。
了解 API 回傳的天氣觀測資料#
根據先前單元,在線上說明文件試打「局屬氣象站-天氣觀測資料 API」取得回應,可以看到回應內容包含幾個部分:

success:是否成功向伺服器發送請求並取得回應result.fields:向伺服器請求的欄位資料,這裡因為在請求時沒有特別限制,預設會回傳全部欄位的資料records.location:列出所有局屬氣象站目前天氣觀測資訊,這是我們最想要的資訊
因為 records 的部分會是我們最主要需要使用到的資料,因此我們再深入檢視一下,可以看到:
locationName:局屬氣象站名稱time:觀測時間weatherElement:各天氣的觀測資料
特別是在 weatherElement 屬性中提供的許多天氣資料中,可以找到我們需要用到的「溫度(TEMP)」、「風速(WDSD)」等資訊,像是:
但從這些資訊中無法看出像是「降雨機率」、「氣象描述」、「白天晚上」、「晴天或降雨」等資訊,因此這個部分未來會需要再靠另一支 API 「一般天氣預報-今明 36 小時天氣預報」來補足這些部分。
info
關於 API 回應中的各欄位意義,都有描述在「局屬氣象站資料集說明檔」中:https://opendata.cwb.gov.tw/opendatadoc/DIV2/A0003-001.pdf。另外,雖然「氣象描述」的部分在文件中有提到可以透過「H_Weather」取得,但實際上會發現回傳的資料大多是 null 或是 -99,即表示沒有資料。
fetch API 的基本使用#
了解「天氣觀測」API 會回應的內容後,就可以實際撰寫一段 AJAX 來向中央氣象局拉取資料,這裡我們使用瀏覽器原生的 fetch API 來發送請求,一般使用 fetch 發送 GET 請求時,只需要在 fetch(<requestURL>) 的方法中帶入 requestURL 作為參數,這個 fetch 會是一個 Promise,因此可以透過 .then 串連伺服器回傳的資料。
程式碼會像這樣:
因此要發送請求,只需將 requestURL 的部分換成中央氣象局提供的 API 網址就可以了。
點擊重新整理按鈕後拉取資料#
可以有幾個不同時間點來向中央氣象局請求資料,一個是在畫面載入時就自動拉取一次,另一個是在使用者點擊「重新整理」按鈕時拉取資料。現在我們先做後者,也就是使用者主動點擊的方式。
我們只需先定義好 handleClick 方法,在 handleClick 內去呼叫中央氣象局 API,接著在 <Refresh /> 按鈕綁上 onClick 事件,當事件被觸發時會呼叫 handleClick 方法,整個過程會像這樣:
- 先將之前取得的授權碼存成一個常數,取名為
AUTHORIZATION_KEY:
提示
在 JavaScript 中對於像是授權碼這類不會變更的常數,習慣以全大寫搭配底線的方式來命名。
- 針對某一地區發送 API 請求,這裡我們先針對台北(讀者也可以輸入其他地區)來請求當前的天氣觀測資料:
提醒
先前的單元中曾提到在「天氣觀測」和「天氣預報」這兩支不同的 API 中,需要使用的 locationName 不同,前者帶入的是「局屬觀測站」,例如「臺北」,後者帶入的是「縣市」,例如「臺北市」,如果帶錯的話將會無法取得正確的回應。這點在後面需要同時處理兩道 API 時會再做更多處理。
現在當 handleClick 被觸發時,就會透過 fetch 向中央氣象局發送請求。
- 最後我們只需要把 handleClick 這個方法透過
onClick綁定在<Refresh>元件上:
順利的話當使用者點擊「臺灣好天氣」右下角的「重新整裡」按鈕時,就會向中央氣象局發送請求,並取得資料,你將可以在瀏覽器的 console 視窗中看到回傳的資料內容:

更新元件內的資料狀態#
現在已經可以在使用者點擊按鈕後,向中央氣象局發送請求並取得回應,但是因為還沒被把這些資料內容帶回到 React 元件中,因此畫面並不會改變,這時候你可能已經想到了,要在改變資料的時候同時讓畫面重新轉譯(render),就可以用 useState() 中回傳給我們的 setCurrentWeather 這個方法。
從 API 回傳的資料來看,我們需要的資料會在 records.location 這個陣列中元素的 weatherElement 屬性中,當中最重要的會是溫度(TEMP)和風速(WDSD):

所以在使用 setCurrentWeather 來把這些資料帶回元件中時,需要先把用得到的資料取出來。
先稍微說明一下這裡的邏輯:
- 定義
locationData把回傳的資料中會用到的部分取出來
這裡透過 reduce 組合出來的 weatherElements 將會長這樣:
- 在取得所需要的資料後(除了
description和rainPossibility的部分需要再透過額外的 API 取得),就可以透過useState回傳的setCurrentWeather方法來更新 React 內的資料狀態:
最後就把上面寫好的邏輯放到原本的 handleClick 方法中:
現在當我們點擊「臺灣好天氣」右下角的重新整理按鈕時,就可以看到當前最新的資料狀態(除了天氣描述和降雨機率)!
useState 中帶入物件時須留意的地方#
前幾個章節中,在實作計數器和網速單位換算器時,useState 的裡面的值都是放入數值,但除了基本的數值、字串、布林之外,保存在 React 元件內的資料狀態也可以是物件或陣列。像是在上面 currentWeather 中我們就是放入物件:
setSomething 會把舊有的資料完全覆蓋#
但要特別留意的是,當我們使用物件時,如果有需要保留物件中原有的屬性時,不能只是在 setCurrentWeather 帶入想要變更的物件屬性,因為 setSomething 這種用法會完全以傳入的值覆蓋掉舊有的內容。
什麼意思呢?假設現在我只想要修改 currentWeather 中 temperature 的值,其他屬性想要保留不變的話,我們不能這樣寫:
因為 setSomething 這種方法會用新給的資料全部覆蓋掉舊有的資料,因此 currentWeather 會變成只剩下 temperature 這個屬性。
正確的做法應該要把舊有的資料透過物件的解構賦值帶入新物件中,再去添加或修改想要變更的屬性,像是這樣:
如此更新後的 currentWeather,才會是先保留了原本的 currentWeather 中的所有屬性後,接著才更新 temperature 屬性的值,而不會變成只剩下 temperature 屬性而已。
要使用多次 useState 還是把所有資料都包在一個物件中只使用一次#
一般來說,在一個 React Component 中多次呼叫 useState 並不會有太大的問題,因此不建議單純只是為了想要少用幾次 useState 而把所有不相關的資料都放到同一個物件中,因為這代表你將只會得到一個 setSomething 的方法,而你只要呼叫到這個方法,因為是用新的資料整個覆蓋掉舊的,因此即使有很多不需要更新的資料,但仍會被迫整個換掉。
因此官方建議,可以將有關聯的資料放在同一個物件中,而沒有關聯的資料,就另外再使用 useState 去定義資料狀態。
換你了!向中央氣象局請求真實的觀測資料#
現在,換你實際透過 AJAX 的方式,向中央氣象局請求真實的資料回來呈現吧!你可以參考下述的步驟:
- 了解「局屬氣象站-現在天氣觀測報告」API 中會回傳的資料內容
- 撰寫
handleClick方法,並將該方法綁定在<Refresh />按鈕的onClick事件上 - 定義在 AJAX 請求中會使用到的常數,包含
AUTHORIZATION_KEY和LOCATION_NAME - 當使用者點擊重新整理的按鈕後,透過
fetch方法向中央氣象局 API 發送請求,並取得回應 - 檢視回應的資料內容,並過濾出我們所需要的資料
- 透過
setCurrentWeather方法來更新 React 元件中的資料狀態 - 確定畫面有因為資料變更而連動更新,顯示當前的天氣資料
本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 get-current-weather-when-refresh-clicked 分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/get-current-weather-when-refresh-clicked
