7-5 Uncontrolled components 和 useRef 的使用

本單元對應的專案分支為:uncontrolled-components-with-use-ref。

單元核心#

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

  • 了解什麼是 Uncontrolled Components
  • 透過 useRef 來取得表單資料

在上一個單元中,提到了表單的兩種處理方式,分別是 controlled components 和 uncontrolled components。在上一個單元中已經說明了 controlled components 的做法,在這個單元中進一步來看如何使用 uncontrolled components 達到相同的結果。

這個單元的 useRef 的說明並不會放入最後完整的程式碼中,主要是作為示範說明用。

為什麼要使用 Uncontrolled Components#

一般來說除非是 <input type="file"> 這種檔案上傳的欄位之外,多會使用 Controlled Component 來做。

但有些時候可能只是想要很簡單的去取得表單中某個欄位的值,或者是有一些情況下需要直接對 DOM 元素進行操作(例如,音樂播放器中有許多方法是直接綁在 <video> 元素上的),這時就可以使用 React 中提供的 useRef 這個 Hooks。

前一個單元在 Controlled Components 的表單中,會使用 useState 搭配 onChange 來隨時更新使用者目前在表單中填入的資料,把茲這些資料放到 React 元件內部的 state 來管理。

但在 Uncontrolled Components 並不會把資料交給 React 管理,而是自己選到該 <input /> 元素後去從該 DOM 元素中把值取出來。在 React 中若想要選取到某一元素時,就可以使用 useRef 這個 React Hooks。

這裡我們就同樣以 WeatherSetting 這個表單為例,只是這次把它作為 Uncontrolled Components 搭配 useRef 來使用。

useRef 的基本用法#

useRef 的基本用法如下:

  • 在 useRef 內可以放進一個預設值(initialValue)
  • useRef 會回傳一個物件(refContainer),這個物件中會有一個名為 current 的屬性,裡面放的會是一開始給的預設值
  • 這個物件最重要的是它不會隨著每一次畫面重新轉譯而指稱到不同的物件,而是可以一直指稱到同一個物件
// 使用 useRef 會回傳一個帶有 initialValue 的物件
const refContainer = useRef(initialValue);

你可以把透過 useRef 取得的物件,當作是完全獨立於 React 元件的變數,它不會受到 React 元件重新轉譯的影響,同樣的,當你對裡面的值進行操作時,和 useState 的 setSomething 不同,useRef 也不會觸發 React 元件重新轉譯。舉例來說:

const fooRef = useRef('foo');
// 在回傳物件的 current 屬性中可以取得原本的值
console.log(fooRef.current === 'foo'); // true
// 可以直接對其修改屬性值,這麼做不會觸發元件重新轉譯
fooRef.current = 'bar';
console.log(fooRef.current); // bar

如果是要把 useRef 當成 document.querySelector 來選取到某一元素的話,可以在該 HTML 元素上使用 ref 屬性,並把 useRef 回傳的物件放進去即可,例如:

const InputElement = () => {
// 透過 useRef 取得一個不帶初始資料的 inputRef
const inputRef = useRef(null);
// 透過在 HTML 元素上使用 ref 屬性,把 useRef 取得的回傳值帶進去
return <input ref={inputRef} />;
};

這時候只要透過 inputRef.current 就可以指稱到 <input /> 這個 HTML 元素,就很像是用 document.querySelector('input') 後取得的結果。

套用到設定頁面#

讓我們把原本 Controlled Components 的寫法移除,實際套用 Uncontrolled Components 到地區設定頁面來看看可以怎麼用:

  1. 先從 react 中取出 useRef 這個 Hook 來用,把原本用的 useState 移除
// ./src/views/WeatherSetting.js
import React, { useRef } from 'react';
  1. 使用 useRef 來建立可以一直被參照到的物件,將這個回傳的物件取名為 inputLocationRef
  2. 在 <input> 的地方,不需要再使用 onChange 事件隨時更新 React 的資料狀態,而是透過 ref={inputLocationRef} 讓 inputLocationRef.current 可以指稱到這個 input 欄位,資料是保存在瀏覽器本身的 input 欄位中
const WeatherSetting = ({ handleCurrentPageChange }) => {
// STEP 2:使用 useRef 建立一個 ref,取名為 inputLocationRef
const inputLocationRef = useRef(null);
return (
{/* STEP 3:將 useRef 回傳的物件,指稱為該 input 元素 */}
<StyledSelect id="location" name="location" ref={inputLocationRef}>
{/* */}
</StyledSelect>
);
};
  1. 定義 handleSave 方法,在該方法中,即可透過 inputLocationRef.current 即可取得剛剛透過 ref 指稱的元素,並且透過 inputLocationRef.current.value 就可以取得該欄位的值
const WeatherSetting = ({ handleCurrentPageChange }) => {
// ...
// STEP 4:透過 inputLocationRef.current 取得透過 ref 指稱的 HTML 元素
const handleSave = () => {
console.log('value', inputLocationRef.current.value);
};
return (
// ...
<ButtonGroup>
<Back onClick={() => handleCurrentPageChange('WeatherCard')}>返回</Back>
<Save onClick={handleSave}>儲存</Save>
</ButtonGroup>
);
};

此時當使用者按下儲存按鈕時,一樣會在瀏覽器的 console 面板中出現使用者想要儲存的地區資訊。

在 Uncontrolled Components 中設定預設值#

對於 Uncontrolled Components 若想要定義預設值,可以在 <input> 欄位中使用 defaultValue:

const WeatherSetting = ({ handleCurrentPageChange }) => {
// ...
return (
// ...
<StyledSelect
id="location"
name="location"
ref={inputLocationRef}
// 透過 defaultValue 設定預設值
defaultValue="臺南市"
>
{/* */}
</StyledSelect>
);
};

如此,當使用者一進來設定頁面時,顯示的選項就會是「臺南市」。此外,由於現在並沒有監控 onChange 事件,也沒有使用 setSomething 的方法來變更 React 元件內的資料狀態,因此不管使用者在中途切換過什麼選項,開發者都不會理會,只要在最後按下儲存按鈕時,才會從該 HTML 元素中把值取出。

提示

你可以發現當我們把 useRef 回傳的物件透過 rel 的方式放到 HTML 元素中時,就很像是用 document.querySelector 去選到該元素後,保存在 useRef 回傳物件的 current 屬性內。

換你了!使用 uncontrolled components 的方式取得使用者欲儲存的地區資訊#

這個單元主要是示範和說明 uncontrolled components 還有 useRef 的使用,但是一般來說,在沒有額外需求的情況下,React 的表單還是會使用 Controlled Components,如此才能知道每一次使用者輸入的內容,做出立即的互動效果(例如,錯誤提示、表單驗證等等)。讀者可以參考以下步驟完成:

  1. 透過 import 匯入 useRef 這個 React Hooks
  2. 在 useRef 中帶入預設值為 null,並取得 inputLocationRef 物件
  3. 透過 ref 這個屬性,讓 inputLocationRef 可以參照到該 select 元件
  4. 在使用者選擇不同的地區時,可以透過 inputLocationRef.current 指稱到 Select 元素
  5. 透過 inputLocationRef.current.value 即可取得使用者選擇的項目值

從這張圖中,可以看到在程式碼上,使用 Controlled Components 和 Uncontrolled Components 上的差異:

Imgur

雖然透過 Uncontrolled Components 的作法一樣可以完成後續要做的保存使用者地區資訊的功能,但目前依照 React 的建議,在下一個單元中,我們仍會使用上一個單元完成的 Controlled Components 的做法,因此練習完後,記得要修改回原本的程式碼。

useRef 的補充說明:在 Functional Component 中建立不會導致畫面更新的變數#

useRef 除了可以用來參照回某個表單元件外,在 React 中也很常用來建立一個不會使得畫面更新的變數來使用,因此開發者可以把一些變數的資料保存在 useRef 取得物件的 current 屬性中,並在需要時進行修改即可。關於這部分更多的補充,可以檢視本單元於 Github 專案分支的說明頁檢視。

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

Imgur