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