iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 22
1
Modern Web

I Want To Know React系列 第 22

I Want To Know React - Uncontrolled component

回顧 React form 與 controlled component

在前兩個章節:初探 Form & Controlled componentControlled component 語法中,我們學習到了在 React 中可以如何處理 form element 的兩種方式:

  • Controlled component:由 React 管控 HTML form element 的 state
  • Uncontrolled component:由 HTML form element 自行管理 state

也知道了 controlled component 的使用情境:

  • 需要對 form 檢查是否修改過(dirty)、格式驗證(validation)...etc 等狀況時
  • 需要取得 form 的 state 或內容去連動修改其他 component 時

也了解了如何使用 controlled component。

在這章節中,我們將開始研究 Uncontrolled component 與其使用方法。

Uncontrolled component

如果一個 form element 的值是由 HTML form element 與 DOM 自己控管的,我們就稱此 form element uncontrolled component。

以下是一個 uncontrolled component 的簡單範例:

const NameForm = (props) => (
	<form>
		<label>
		Name:
	    <input type="text" />
    </label>
  </form>
);

為何需要 uncontrolled component

與 controlled component 不同,uncontrolled component 的輸入內容是由 form element 自己管理。若要取用 form 內容,則是直接透過 DOM 來操作。

雖說這樣就缺少了讓 React state 變成 Single Source Of Truth 的優點,但是反過來說,uncontrolled component 可以自己管理 form 的內容,在某些特殊狀況使用上可能會比較方便(e.g. 跟非 React 程式整合)。

另外,一些特殊的 HTML form,像是 file input,並因為安全性問題並不支援從 JavaScript 修改 input 內容。在這種情形下,uncontrolled component 就是必須的,因為 React 並無法介入此類 form 的內容,因此必須將 state 交給原生 HTML form element 自己管理。

何時需要使用 uncontrolled component

綜合剛剛介紹的 uncontrolled component 的優點,可以整理出以下的狀況使用 uncontrolled component 是個可考慮的選擇:

  • 跟非 React 的程式碼整合時

    如上個段落所講,有時讓 DOM element 與開發者自己管理 form 會讓跟非 React 的程式整合時方便一些。

  • 處理特殊 form element 時,如 file input

    如上個段落所述,因為安全性考量,如 file input 的 form element 不允許 JavaScript(React)存取內容,因此遇到這種 form element 時就必須使用 uncontrolled element。

  • 不需要取用 form 的 state 時

    如果 form 的行為十分簡單,不涉及 state 存取/操作(e.g. 驗證、檢查 ...etc)的話,uncontrolled component 會是一個可考慮的選項,因為 uncontrolled component template code 的量會比 controlled component 少一些( valueonChangestate 初始/設定 )。另外也可以減少 component 中一些不必要的 state。

然而如果沒有特殊理由的話,在大多數的狀況下還是推薦使用 controlled component。

Uncontrolled component 語法

創建 uncontrolled component

創建出 uncontrolled component 的方式非常簡單,只要不使用 value prop 的 form element 即為 uncontrolled component:

<input type="text" />

在這語法下,使用者可以正常的輸入內容,而此內容不會受到 React 管控。

但如果這時候可以使用 [<form action="">](https://www.w3schools.com/html/html_forms.asp) 來傳送資料,但如果要取用 input 內容的話,則還要多做一些設定,就繼續往下看吧!

取用 uncontrolled component value

因為 uncontrolled component 的 state 是完全由 DOM 自己維護的,因此如果要取用 uncontrolled component 的值的話就要想辦法取用 DOM。

取用 DOM 的方法是使用 ref。在 input element 上設定 ref prop:

<input type="text" ref={this.input} />

Ref 的詳細內容將在之後章節介紹。簡單來講,ref 搭配 form 使用時會有幾個重點:

  1. 先在 constructor 中用 React.createRef()

  2. 並將創建出來的 ref 物件放入 form 的 ref prop 裡面

    將 ref 物件加入 form 的 ref 後,React 就會把 form 的 DOM element 放入 ref 物件的 current 屬性之中

  3. 要取用 form DOM element 的值時,透過 ref 物件的 current.value 拿取輸入的內容

以下是一個提供輸入姓名並提交的簡單範例:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

讀者也可以到 CodePen 中實際嘗試。

範例中的 ref 與 form 操作方式就如剛剛所述,主要有三個語法重點:

  1. constructor 中用 React.createRef() 將 ref 創建出來放到 this.input 中。
  2. 並將創建出來的 ref 物件放入 text input 的 ref prop 裡面,這樣在 render 時,React 就會把 form 的 DOM element 放入 ref 物件的 current 屬性之中。
  3. 當點下 Submit 按鈕時會觸發 handleSubmit,此時因為需要取用 text input 的 value 以顯示到畫面上,因此使用 this.input.current.value 來取用 text input 的內容。

使用 file input

如前面段落所提,因為安全性的關係,瀏覽器規定 file input elemnt 只能由使用者手動設定,不可使用 JavaScript 設定。因此 file input 必須為 uncontrolled component。

基本上,創建 file input 的語法也跟一般 form element 的大同小異,只差在取用 file input 內容需要使用 File API

以下將示範如何使用 file input 取得檔名:

class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();
  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`
    );
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

ReactDOM.render(
  <FileInput />,
  document.getElementById('root')
);

讀者也可以到 CodePen 上實際嘗試。

取用 file input 的語法重點:

  1. constructor 中用 React.createRef() 將 ref 創建出來放到 this.fileInput 中。
  2. 並將創建出來的 ref 物件放入 file input 的 ref prop 裡面,這樣在 render 時,React 就會把 form 的 DOM element 放入 ref 物件的 current 屬性之中。
  3. 當點下 Submit 按鈕時會觸發 handleSubmit,此時因為需要取用 file input 的 value 以顯示到畫面上,因此使用 this.fileInput.current.filename[0].name 來取用 file input 的內容。

如果要取用檔案實際內容的話,可以參考 File API

設定輸入預設值

在 uncontrolled component 中也可以設定輸入內容的預設值,然而其語法與 HTML 有些許的不同。

在 HTML 中,如果要給 form 相關 element 預設值的話,使用 value prop 即可。如下所示:

  • Text input / Select / Textarea 使用 value="defaultValue"
  • Checkbox / Radio input 使用 checked
<input type="text" value="Henry" />
<input type="radio" checked />

然而還記得嗎?在 React 中,只要為 form element 加上 value prop,就會使此 form element 變成 controlled component。

為了解決這個問題,React 把設定 uncontrolled component 的輸入預設值 prop 改名了,如此就不會與 controlled component 的 value prop 衝突:

  • Text input / Select / Textarea 使用 defaultValue
  • Checkbox / Radio input 使用 defaultChecked
<input type="text" defaultValue="Henry" />
<input type="radio" defaultChecked />

以下是實際範例:

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

另外,需要注意的是,defaultValue prop 是 uncontrolled component 特有的 prop,不應在 controlled component 中使用。

如果執意在 controlled component 中使用則會出現以下警告:

<input type="text" defaultValue="test" value={this.state.input} />
// "Warning: %s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both).

注意事項

是否設定 form ref 不代表 form 就是 uncontrolled component

讀者可能會認為設定 ref 就代表 form 是 uncontrolled component,或者 controlled component 不能設定 ref

然而,只要沒有設定 value prop 的 form element 就是 uncontrolled component,與是否設定 ref 無關。對於 uncontrolled component 來說,ref 只是一種取用 form 內容的手段而已。

另外,controlled component 也是可以設定 ref 的:

<input type="text" value="123" ref={this.input} />

更完整的範例可以參考 CodePen

Controlled component vs. Uncontrolled component

統整 Controlled component 篇以及這篇學到的內容,就可以比較 controlled component 與 uncontrolled component 之間有什麼區別了。

我們可以把差異整理成以下這張表格:

Controlled component vs. Uncontrolled component Controlled component Uncontrolled component
State(輸入內容)控管方式 由 React 統一控制 由 HTML form element 自己控制
使用情境 需要使用 form 的內容做進一步的驗證、檢查或是調整精細 UI/UX 時 與非 React 程式整合時,或者使用 file input 時
宣告語法 有 value prop 沒有 value prop
設定輸入內容的方式 setState 把值 "推送" 給 input 預設值透過 defaultValue prop,其餘狀況直接透過 DOM 設定
取得輸入內容的方式 拿 component 的 state 透過 DOM 語法,把值 "拉取" 下來

小結

在此章節中,我們學習到了 uncontrolled component 就是自行管理 state 的 HTML form element 。也了解了 uncontrolled component 的使用情境:

  • 跟非 React 的程式碼整合時
  • 處理特殊 form element 時,如 file input
  • 不需要取用 form 的 state 時

並知道了 uncontrolled component 的創建 / 取用語法,以及如何設定其預設值。

參考資料


上一篇
I Want To Know React - Controlled component 語法
下一篇
I Want To Know React - 初探提升 state
系列文
I Want To Know React30

尚未有邦友留言

立即登入留言