7-4 React 中的表單處理(Controlled vs Uncontrolled)
本單元對應的專案分支為:controlled-components-of-form
。
#
單元核心這個單元的主要目標包含:
- 了解 Controlled 和 Uncontrolled Components 的概念
- 了解如何使用 Controlled Components 來操作表單資料
在上一個單元中,使用者已經可以自由的在天氣資訊頁(WeatherCard)和設定頁(Setting)間切換。在這個單元中會說明在 React 中基本的表單處理,讓 React 元件可以知道使用者選擇的天氣選項為何:
#
Controlled vs Uncontrolled Components關於表單的處理,我們曾經在網速單位換算器中使用過表單元素 <input type="number" />
,當時透過 onChange
搭配 setState
的方式來操作表單的資料,但在當時我們還沒有進一步說明 React 中表單處理的概念。
在 React 中表單元素的處理主要可以分成兩種 Controlled 和 Uncontrolled 這兩種,這裡關於 Controlled 和 Uncontrolled 指的是「資料受不受到 React 所控制」,也就是「受 React 所控制的資料(Controlled)」或「不受 React 所控制的資料(Uncontrolled)」。
之所以在表單元素上會區分「受 React 控制的資料」和「不受 React 控制的資料」,主要是因為在瀏覽器中,像是 <input />
這類的表單元素本身就可以保有自己的資料狀態,這也就是爲什麼當我們在 <input />
中輸入文字後,可以直接透過 JavaScript 選到該 input 元素後,再取出該元素的值,因為使用者輸入的內容(資料)可以直接保存在 <input />
元素內。
以下面程式碼舉例來說:
- 我們可以透過
document.querySelector
選到該表單元素 - 透過該元素的
value
屬性,就可以知道該<input />
欄位中填入的值為何
但到了 React 時,React 就可以幫我們處理資料狀態,我們可以把表單內使用者輸入的資料交給 React,在使用者輸入資料的同時驗證使用者輸入內容的有效性(例如,輸入的內容有誤時跳出提示訊息),並做瀏覽器畫面的更新。
這種把表單資料交給 React 來處理的就稱作 Controlled Components,也就是受 React 控制的資料;相對地,如果不把表單資料交給 React,而是像過去一樣,選取到該表單元素後,才從該表單元素取出值的這種做法,就稱作 Uncontrolled Components,也就是不受 React 控制的資料。
針對表單元素, React 會建議我們使用 Controlled Components,基本上使用 Controlled Components 和 Uncontrolled Components 都能達到一樣或類似的效果,但是當我們需要對資料有更多的控制或提示畫面的處理時,使用 Controlled Components 會來得容易的多。
⚠️ 提醒:多數的表單元素都可以交給 React 處理,除了上傳檔案用的
<input type="file" />
例外,因為該元素有安全性的疑慮,JavaScript 只能取值而不能改值,也就是透過 JavaScript 可以知道使用者選擇要上傳的檔案為何(取值),但不能去改變使用者要上傳的檔案(改值)。因此對於檔案上傳用的<input type="file" />
只能透過 Uncontrolled Components 的方式處理。
在有了 Controlled 和 Uncontrolled Components 的概念後,現在就來讓我們看看如何實作這兩者表單處理的方式。
在這個單元中我們會先練習 Controlled Components 的表單處理,下一個單元再來看看,如果改成 Uncontrolled Components 的話可以怎麼做。不論是使用 Controlled 或 Uncontrolled 的方法,當使用者在天氣設定頁點選「儲存」的時候,都可以在瀏覽器的開發者工具中看到使用者欲儲存的地區,但實際上資料的保存則會到後面的單元再繼續說明。
#
Controlled Components針對表單元素,React 會建議我們使用 Controlled Components,也就是把表單的資料儲存在該 React 元件內交給他來處理,這個做法就和先前網速單位換算器中使用的方式相同。
#
將資料交給 React因為要將資料交給 React 處理,所以會先透過 useState
來建立保存資料狀態的地方,接著在表單元素上透過 onChange
事件來取得該表單元素當前的值,並且馬上更新到 React 元件的資料狀態內。
套用到 WeatherSetting 天氣設定頁面就像這樣:
- 從 react 中載入
useState
- 透過
useState
取得locationName
和setLocationName
,將預設值先設為「臺北市」:
⚠️ 提示:這裡不把資料狀態取名為
location
的原因在於瀏覽器本身就有一個window.location
物件,因此當你直接在瀏覽器的 console 面版中輸入location
是會得到內容的,為了避免可能的錯誤,取名為locationName
- 使用
onChange
事件來監聽使用者輸入的資料,並且當事件觸發時呼叫handleChange
定義
handleChange
函式,當使用者輸入資料時,把資料內容透過setLocation
更新 React 內部的資料狀態把使用者輸入的內容透過
setLocationName
更新到 React 內的資料狀態
現在當使用者點選某一個地區時,我們將可以從 React Developers Tools 中看到使用者選擇的項目:
#
帶入資料預設值透過 React Developer Tools 你會發現,當使用者一進來這頁時,因為 locationName
的預設值是空字串,因此除非使用者有點擊地區選項,否則雖然顯示 React Developers Tools 中資料「臺北市」,但畫面並沒有連帶對應。但如果使用者前一次已經有儲存過地區資料,當使用者在進到設定頁的時,地區欄位應該就會直接帶出上一次他填寫的資訊,在 Controlled Components 中可以怎麼做呢?
若要讓這個地區欄位的 <input>
在頁面呈現時就帶有預設值的話,我們可以直接在 <input>
中加上 value
屬性,React 會自動把這個 value
帶入的值當作該欄位的預設值呈現出來:
#
建立點擊儲存後的行為流程現在當使用者點擊儲存按鈕時,我們只需要把使用者選擇的項目透過 console.log 顯示出來,在後面的單元再來實作儲存的功能,因此:
- 定義
handleSave
函式 - 在儲存按鈕透過
onClick
綁定事件,並觸發handleSave
方法
這時候當使用者點擊儲存時,即可在瀏覽器開發者工具的 console 面板看到使用者欲儲存的資料:
#
換你了!取得使用者欲儲存的地區資訊在這個單元中我們說明了 controlled components 的做法,也就是透過 useState
,表單中的 onChange
和 value
來達到把資料交給 React 內部的資料狀態進行管理,最後當使用者點擊「儲存」按鈕時,可以將使用者欲保存的地區資訊顯示在 console 面板中。
現在換你實際練習看看:
- 透過
useState
取得locationName
和setLocationName
方法,預設值為「臺北市」 - 定義
handleChange
方法,當表單的資料改變時,把最新的資料透過setLocationName
保存在 React 內部的資料狀態中 - 將
onChange
事件綁定在 select 元素上,當資料改變時觸發handleChange
方法 - 定義
handleSave
方法,單純先把locationName
的資料console
出來 - 當使用者點擊儲存時,觸發
handleSave
方法
在下一個單元中,會接著說明如何用 uncontrolled components 來達到相同的功能。
本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 controlled-components-of-form
分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/controlled-components-of-form