6-2 根據天氣代碼顯示天氣圖示 - useMemo 的使用
本單元對應的專案分支為:prevent-redundant-computation-with-use-memo。
#
單元核心這個單元的主要目標包含:
- 根據天氣代碼顯示對應的天氣圖示
- 了解
useMemo
的使用方式與使用時機
在上一個單元中,已經能夠根據天氣代碼轉換為天氣型態,最後再轉換為天氣圖示。在這個單元中要來實作天氣圖示的呈現,也就是讓天氣圖示能夠隨著天氣的不同而有變化,同時也會說明 React Hooks 中 useMemo
的使用時機與方式。
#
將天氣代碼的資料從 App 元件傳入 WeatherIcon#
App 元件由於天氣代碼的資料是在 App
元件中取得,WeatherIcon
並不知道,因此會透過 props
從 App 元件把資料傳入 WeatherIcon 元件,這裡把這個 props 的名稱取為 weatherCode
。
因為天氣圖示共分成白天和晚上兩種,這裡我們使用 moment
這個變數來判別是白天還是晚上。但目前因為從 API 只能取得「天氣代碼」,還沒有辦法得知當時是白天(day)或晚上(night),因此把 moment
先固定為晚上(night
),以 props 的方式傳入元件中,而白天晚上的判斷會在後面的單元進行處理。
這裡會在 WeatherIcon
這個元件中透過 props 方式,把 weatherCode
和 moment
程式碼的變動如下:
#
WeatherIcon 元件在 ./src/components/WeatherIcon.js
的 WeatherIcon 元件中,已經可以透過 props 取得 App 元件傳進來的 weatherCode
和 moment
:
現在要呈現天氣圖示的方式很簡單,因為我們先前已經定義好了 weatherIcons
這個物件,透過這個物件,只要知道目前是白天或晚上(moment),接著知道天氣型態(weatherType),就可以得到對應的天氣圖示(weatherIcon)。所以可以像這樣寫:
是不是相當簡潔呢?
這時候你應該可以看到畫面上已經出現對應的天氣圖示。
#
留意 weatherCode 和 moment 的值目前因為 moment
這個 props 在 App 元件中是先固定寫為 night
,所以 WeatherIcon 元件中的 moment
變數也會固定是 night
,所以畫面上呈現的也會是晚上的天氣圖示(月亮),關於白天和晚上的判斷,會在後面的單元再繼續進行處理。
而 weatherCode
這個 props,會實際上透過 AJAX 的方式從中央氣象局的 API 取得。在這裡由 App 元件傳進來的 weatherCode
在還沒有從 API 拉取到資料前,會使用 useState
中所定義的預設值,也就是 0
,並以 props 的方式傳入 WeatherIcon 元件中,也就是說一開始時,weatherCode
的值會是 0。
接著,要等到透過 AJAX 取得中央氣象局的資料回應後,App 元件會再把最新的 weatherCode
的值透過 props 再次傳入 WeatherIcon 元件中,這時候 WeatherIcon 元件才能取得最新的天氣代碼。
這裡你也可搭配先前安裝的 React DevTools,試著選到 WeatherIcon 元件後,去修改裡面 props 的 moment
和 weatherIcon
,看看天氣圖示會不會有不同的變化,例如,你也可以將 moment
的值從 night
改成 day
,應該會看到天氣圖示從「夜晚的月亮」變成「白天的太陽」:
#
props 更新時子層元件也會更新看到這裡讀者可能會有些好奇,為什麼這裡沒有使用 useState
提供的 setSomething
這種寫法,元件仍然會重新轉譯呢?
還記得最早的時候,我們直接改變元件內的變數時,元件的畫面並不會隨著變數改變而更新的情形嗎?那為什麼這裡 weatherCode 改變時,元件會重新更新呢?
這是因為現在 WeatherIcon 這個元件中,雖然沒有其內部的資料狀態(state),但有從父層 App 元件傳進來的 props,而 props 同樣算是在該元件內的資料狀態,只是它是從父層而來的。在 React 中,只要元件的資料狀態有改變,不論是該元件本身的 state 、或者是父層元件傳入的 props,都會觸發該元件重新轉譯。
提示
React 可觀測到的資料狀態都可以透過 React Developer Tool 加以檢視,在這裡面你可以看到每個元件內所擁有的 state 和 props。
因此回到 WeatherIcon 元件中,雖然這裡沒有使用 useState,但因為父層 App 元件在透過 AJAX 取得 weatherCode 的值後,會再次透過 props 傳到 WeatherIcon 元件中,props 改變了,就觸發 WeatherIcon 元件重新轉譯。
#
以 useMemo 提升效能 - 避免不必要的重複運算在上面的程式碼中,做法並沒有什麼問題,但你會發現只要元件有重新轉譯的情況,例如,只要 weatherCode 或 moment 的資料有變動,或者未來這個元件中有其他 state,只要元件一更新,就需要透過 weatherCode2Type
這個方法重新取得 weatherCode。
如果是 weatherCode 改變而重新透過 weatherCode2Type
來取得最新的 weatherType 還算合理,但若是 moment 或其他 state 改變導致的畫面更新,明明 weatherCode 沒變,卻還要重新使用 weatherCode2Type
取得完全相同的結果,就顯得有些多餘。
這裡 weatherCode2Type
運算上不算太複雜,所以重複計算對效能的影響並不太大,但若未來函式需要做更複雜的運算,只要有資料狀態改變,即使變更的資料對計算結果沒有影響卻都需要重新計算的話(例如,變更的不是 weatherCode,卻又重新呼叫 weatherCode2Type
) ,會使得效能變差,嚴重時 JavaScript 無法進行其他處理,也就是所謂的阻塞(blocking),將會使得使用者的體驗非常差。
為了解決這個問題,React 提供了 useMemo
這個 Hook 來幫助我們提升效能。
#
useMemo 的基本使用useMemo
這個 React Hook 和先前單元中提到的 useCallback
非常類似,主要目的都是效能優化的方法之一,兩者的差別主要在於 useCallback 回傳的是一個函式,而 useMemo
回傳的是一個計算好的值。
現在看一下 useMemo 的寫法:
- 首先可以看到和多數的 React Hooks 一樣,它也有 dependencies array,只要 dependencies array 中的變數沒有任何改變,useMemo 中的函式內容就不會執行。
- useMemo 的參數同樣是一個函式,在這個函式中可以進行一些複雜的運算,例如這裡的
computeExpensiveValue
或專案中weatherCode2Type
這類的方法。 - 在 useMemo 函式的最後,只需要把經過複雜運算的「值」回傳出來即可。
現在只要 dependencies array 的變數沒有改變,useMemo
內的函式就不會重複執行,而會直接取得上次運算得到的結果,如此來達到效能的提升。
#
將 weatherCode2Type 的運算結果保存下來回到 WeatherIcon 元件中,我們就可以透過 useMemo
這個 React Hook 來把 weatherType 保存下來,只要 weatherCode 沒有改變的情況下,就不需要透過 weatherCode2Type
重新運算取得 weatherType。
我們可以這麼做:
- 從 react 套件中取出
useMemo
方法
- 透過
useMemo
取得並保存weatherCode2Type
計算的結果,回傳的結果一樣取名為weatherType
,你可以看到,只要把weatherCode2Type
得到的運算結果作為回傳值即可。
- 最後不要忘了在
useMemo
的 dependencies,在陣列中放入weatherCode
,當weatherCode
的值有變化的時候,useMemo
就會重新計算取值:
現在畫面上的天氣圖示一樣會根據天氣代碼加以改變,而且有了 useMemo,只有在 weatherCode 有改變的情況下,才會重新透過 weatherCode2Type
取得新的天氣型態。
#
useMemo 的目的是用來提升效能在這個基本的範例中,你可能沒辦法馬上感受到使用 useMemo 的作用,useMemo
和 useCallback
都是作為效能優化的工具之一,這裡使用 useMemo
的目的是避免當元件重新轉譯但函式帶入的參數(weatherCode
)相同時,所導致不必要的運算,因為 weatherCode
相同的情況下,回傳的 weatherType
一定也相同,根本不需要重新再算一次。
和 useCallback
一樣,多數的情況下沒有使用 useMemo
程式依然能夠正確運行,適當的使用這些優化效能的方法,可以幫助程式減少不必要的重複運算,但不適當或多餘的使用卻仍有可能導致效能變差。
#
換你了!練習看看 useMemo 的使用現在要請你把 weatherCode
和 moment
的資料透過 props 從 App 元件傳到 WeatherIcon 元件內,接著在 WeatherIcon 元件中搭配 weatherCode2Type
的方法,把天氣代碼轉成天氣型態,最後透過 weatherIcons
轉成要顯示的天氣圖示。
你可以參考下面的步驟:
- 在 App 元件中將
weatherCode
和moment
以 props 傳入 WeatherIcon 元件,moment
的值先固定為night
- 在 WeatherIcon 元件取得由 App 元件傳進來的
weatherCode
和moment
props - 將取得的
weatherCode
透過weatherCode2Type
這個方法轉換為天氣型態weatherType
- 透過
weatherIcons
將weatherType
轉換為最終要顯示的天氣圖示(weatherIcon
) - 使用
useMemo
這個方法避免不必要的重複運算 - 試著透過 React Developer Tools 修改
moment
和weatherCode
的值,檢視天氣圖示的改變
本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 prevent-redundant-computation-with-use-memo
分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/prevent-redundant-computation-with-use-memo