5-2 將天氣資料呈現於畫面中 - useState 的使用

本單元對應的專案分支為:create-current-weather-state

單元核心#

這個單元的主要目標包含:

  • 根據畫面設計所需要的資料欄位
  • 使用 useState 定義並使用所需的資料狀態

在上一個單元中,我們已經申請了使用中央氣象局 API 的授權碼,並且說明了在「臺灣好天氣」這個專案中會使用到的兩支 API。在實際向中央氣象局 API 發送請求前,會先在 React 元件中定義會使用到的資料狀態,並把這些資料呈現在畫面上,等到資料能正確在畫面上顯示後,才會在實際串接 API 取得的資料。

使用 useState 定義資料狀態#

首先根據我們設計的畫面,可以先規劃所需要的資料欄位有哪些:

Imgur

接著便可以開始透過 useState 來定義這些所需要的資料欄位:

const App = () => {
const [currentTheme, setCurrentTheme] = useState('light');
// 定義會使用到的資料狀態
const [currentWeather, setCurrentWeather] = useState({
locationName: '臺北市',
description: '多雲時晴',
windSpeed: 1.1,
temperature: 22.9,
rainPossibility: 48,3,
observationTime: '2020-12-12 22:10:00',
});
// ...
}
tip

小提醒:在帶入資料時,並不是完全憑空捏照,而是參考 API 給的回應格式填入。

將資料狀態帶入 JSX 中#

接著我們就把這些資料帶入到 JSX 中原本寫死數值的地方:

Imgur

可以看到,原本寫死在 JSX 中的資料,現在都改成用 useState 產生的 currentWeather 這個物件,這樣未來只要 currentWeather 內的資料有改變時,React 就會自動幫我們更新畫面。

優化資料呈現#

現在我們看到的溫度(temperature)會出現小數點,而最後觀測時間(observationTime)則不是我們習慣的格式,我們針對這個部分來進行優化。

溫度#

針對溫度的部分,可以使用 Math.round() 做四捨五入。改成這樣:

<Temperature>
{Math.round(currentWeather.temperature)} <Celsius>°C</Celsius>
</Temperature>

最後觀測時間#

對於最後觀測時間,我們得到的資料是 2020-12-12 22:10:00,我們希望可以顯示 下午10:10 這樣就好。

要達到這個效果的做法有很多,這裡我們可以使用瀏覽器原生的 Intl 這個方法,這個方法的全名是 Internationalization API,它可以針對日期、時間、數字(貨幣)等資料進行多語系的呈現處理,相當方便,我們可以先將 <Refresh> 中的程式碼改成:

<Refresh>
最後觀測時間:
{new Intl.DateTimeFormat('zh-TW', {
hour: 'numeric',
minute: 'numeric',
}).format(new Date(currentWeather.observationTime))} <RefreshIcon />
</Refresh>
提醒

這裡你會看到 {' '} 的用法,這是因為在 JSX 中預設的空格最後在網頁呈現時都會被過濾掉,因此如果你希望最後在頁面上元件與元件間是留有空格的,就可以透過帶入「空字串」的方式來加入空格。

Intl.DateTimeFormat(<地區>, <設定>) 這個方法中

  • 第一個參數放的是地區,台灣的話使用 zh-TW
  • 第二個參數放的是一些設定值,例如我們希望以數值呈現「時」和「分」就好
  • 最後透過 .format(<時間>) 把時間帶進去之後,就會看到格式化後的時間
補充

關於 Intl 這個方法,有興趣的話可以進一步參考 MDN 官方文件的說明:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl

現在當我們透過 npm start 把專案啟動時,就會看到「溫度」和「觀測時間」的顯示方式比之前的樣子更友善:

Imgur

處理跨瀏覽器問題#

現在當我們使用 Chrome 或 Firefox 開啟這個頁面時,並不會出現任何錯誤,但若你是 Safari 的使用者(在 iOS 行動裝置大多都是使用 Safari),將會看到如下錯誤訊息,並顯示 RangeError: date value is not finite in DateTimeFormat format()

Imgur

之所以會有這個錯誤產生,是因為我們從中央氣象局取得的時間資料是 2020-12-12 10:31:00 這樣的格式,當我們使用 new Date('2020-12-12 10:31:00') 試著把這個時間轉成合法的 JavaScript 時間物件時,在 Chrome 或 Firefox 是可以的,但在 Safari 並不支援這種用法。

你可以在各瀏覽器開發者工具的 console 面板中輸入:

new Date('2020-12-12 10:31:00');

在幾個瀏覽器中會得到不同的回應:

// Chrome
Sat Dec 12 2020 10:31:00 GMT+0800 (Taipei Standard Time)
// Firefox
Date Sat Dec 12 2020 10:31:00 GMT+0800 (台北標準時間)
// Safari
Invalid Date

你會發現就 Safari 最特別...硬是跟你說這是無效的日期格式。沒錯,跨瀏覽器問題永遠是每個前端工程師心中最軟的那一塊。

好在這個問題並不難處理,因為時間的內容算是大大小小的網站都會用到的東西,所以通常已經有許多開發者一起開發開源套件,讓大家都可以用更簡便且支援跨瀏覽器的方式來處理這些問題。

安裝 dayjs#

這裡我們使用一個很輕量的時間處理工具,稱作 dayjs,安裝的方式你應該不陌生了,在終端機中輸入:

$ npm install --save dayjs

使用 dayjs 處理跨瀏覽器時間問題#

dayjs 的功能很多,這裡我們先單純用來處理跨瀏覽器的問題。要使用這個工具前,一樣要先記得載入:

// ./src/App.js
// ...
import dayjs from 'dayjs';

接著在原本使用 new Date() 將日期字串轉換成 JavaScript 日期物件的地方,改成使用 dayjs(),也就是將 .format(new Date(...)) 改成 .format(dayjs(...))

<Refresh>
最後觀測時間:
{new Intl.DateTimeFormat('zh-TW', {
hour: 'numeric',
minute: 'numeric',
}).format(dayjs(currentWeather.observationTime))} <RefreshIcon />
</Refresh>

改用 dayjs 後世界再次恢復了和平,現在在 Safari 中也可以正確解析日期格式。

換你了!使用 useState 定義資料狀態#

現在要換你在 App 元件中定義畫面中會使用到的資料狀態。同樣可以參考下面的流程:

  • 使用 useState 取得「資料狀態(currentWeather)」和「修改資料狀態(setCurrentWeather)」的方法
  • 根據畫面中所需要的資料,以及 API 回應資料 ,在 currentWeather 中定義各資料欄位預設值
  • 把原本寫死在 JSX 中的資料改成透過 currentWeather 這個資料狀態來呈現
  • 優化「溫度」和「觀測時間」的顯示
  • 處理時間在跨瀏覽器上的問題

本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 create-current-weather-state 分支檢視: https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/create-current-weather-state

Imgur

另外由於專案實作的程式碼較為複雜,再次提醒讀者在每一個單元中都可以善加利用 Github 上的「時鐘」圖示來檢視每個單元程式碼的變化:

Imgur

在 commit 中將會清楚呈現每個單元程式碼的變化:

Imgur